From 20cbf66ae406f3c38a0e09a14aaf43ae6af9e79c Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:01:09 -0500 Subject: [PATCH 1/2] feat: spec.generateContractClient; AssembledTx - new e2e tests copied from cli `ts-tests` for the generated bindings, but with TypeScript removed because the ContractClient is generated here dynamically at run time, so we cannot know the types at compile time in the tests. - generate JSON specs from local .wasm files during initiaze.sh instead of generating TS bindings. As explained in the new wasms/specs/README, this is a bummer, but is temporary --- .cargo/config.toml | 129 +++++ .env | 2 + .github/workflows/e2e.yml | 39 ++ .gitignore | 3 + package.json | 16 +- src/contract_spec.ts | 84 ++- src/index.ts | 2 +- src/soroban/assembled_transaction.ts | 731 ++++++++++++++++++++++++++ src/soroban/contract_client.ts | 65 +++ src/soroban/index.ts | 2 + test/e2e/.gitignore | 6 + test/e2e/.nvmrc | 1 + test/e2e/initialize.sh | 112 ++++ test/e2e/src/test-custom-types.js | 194 +++++++ test/e2e/src/test-hello-world.js | 35 ++ test/e2e/src/test-methods-as-args.js | 21 + test/e2e/src/test-swap.js | 137 +++++ test/e2e/src/util.js | 64 +++ test/e2e/wasms/README.md | 16 + test/e2e/wasms/specs/README.md | 10 + test/e2e/wasms/test_custom_types.wasm | Bin 0 -> 17388 bytes test/e2e/wasms/test_hello_world.wasm | Bin 0 -> 7205 bytes test/e2e/wasms/test_swap.wasm | Bin 0 -> 2002 bytes test/e2e/wasms/test_token.wasm | Bin 0 -> 7471 bytes test/unit/spec.json | 71 +-- test/unit/spec/contract_spec.ts | 193 ++++--- test/{ => unit}/tsconfig.json | 6 +- yarn.lock | 471 ++++++++++++++++- 28 files changed, 2282 insertions(+), 128 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 .env create mode 100644 .github/workflows/e2e.yml create mode 100644 src/soroban/assembled_transaction.ts create mode 100644 src/soroban/contract_client.ts create mode 100644 test/e2e/.gitignore create mode 100644 test/e2e/.nvmrc create mode 100755 test/e2e/initialize.sh create mode 100644 test/e2e/src/test-custom-types.js create mode 100644 test/e2e/src/test-hello-world.js create mode 100644 test/e2e/src/test-methods-as-args.js create mode 100644 test/e2e/src/test-swap.js create mode 100644 test/e2e/src/util.js create mode 100644 test/e2e/wasms/README.md create mode 100644 test/e2e/wasms/specs/README.md create mode 100755 test/e2e/wasms/test_custom_types.wasm create mode 100755 test/e2e/wasms/test_hello_world.wasm create mode 100755 test/e2e/wasms/test_swap.wasm create mode 100755 test/e2e/wasms/test_token.wasm rename test/{ => unit}/tsconfig.json (82%) diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..299a92b5a --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,129 @@ +# paths = ["/path/to/override"] # path dependency overrides + +[alias] # command aliases +install_soroban = "install --version 20.1.1 --root ./target soroban-cli --debug" +# b = "build --target wasm32-unknown-unknown --release" +# c = "check" +# t = "test" +# r = "run" +# rr = "run --release" +# recursive_example = "rr --example recursions" +# space_example = ["run", "--release", "--", "\"command list\""] + +[build] +# jobs = 1 # number of parallel jobs, defaults to # of CPUs +# rustc = "rustc" # the rust compiler tool +# rustc-wrapper = "…" # run this wrapper instead of `rustc` +# rustc-workspace-wrapper = "…" # run this wrapper instead of `rustc` for workspace members +# rustdoc = "rustdoc" # the doc generator tool +# target = "wasm32-unknown-unknown" # build for the target triple (ignored by `cargo install`) +# target-dir = "target" # path of where to place all generated artifacts +# rustdocflags = ["…", "…"] # custom flags to pass to rustdoc +# incremental = true # whether or not to enable incremental compilation +# dep-info-basedir = "…" # path for the base directory for targets in depfiles + +# [doc] +# browser = "chromium" # browser to use with `cargo doc --open`, +# # overrides the `BROWSER` environment variable + +# [env] +# # Set ENV_VAR_NAME=value for any process run by Cargo +# ENV_VAR_NAME = "value" +# # Set even if already present in environment +# ENV_VAR_NAME_2 = { value = "value", force = true } +# # Value is relative to .cargo directory containing `config.toml`, make absolute +# ENV_VAR_NAME_3 = { value = "relative/path", relative = true } + +# [future-incompat-report] +# frequency = 'always' # when to display a notification about a future incompat report + +# [cargo-new] +# vcs = "none" # VCS to use ('git', 'hg', 'pijul', 'fossil', 'none') + +# [http] +# debug = false # HTTP debugging +# proxy = "host:port" # HTTP proxy in libcurl format +# ssl-version = "tlsv1.3" # TLS version to use +# ssl-version.max = "tlsv1.3" # maximum TLS version +# ssl-version.min = "tlsv1.1" # minimum TLS version +# timeout = 30 # timeout for each HTTP request, in seconds +# low-speed-limit = 10 # network timeout threshold (bytes/sec) +# cainfo = "cert.pem" # path to Certificate Authority (CA) bundle +# check-revoke = true # check for SSL certificate revocation +# multiplexing = true # HTTP/2 multiplexing +# user-agent = "…" # the user-agent header + +# [install] +# root = "/some/path" # `cargo install` destination directory + +# [net] +# retry = 2 # network retries +# git-fetch-with-cli = true # use the `git` executable for git operations +# offline = true # do not access the network + +# [net.ssh] +# known-hosts = ["..."] # known SSH host keys + +# [patch.] +# # Same keys as for [patch] in Cargo.toml + +# [profile.] # Modify profile settings via config. +# inherits = "dev" # Inherits settings from [profile.dev]. +# opt-level = 0 # Optimization level. +# debug = true # Include debug info. +# split-debuginfo = '...' # Debug info splitting behavior. +# debug-assertions = true # Enables debug assertions. +# overflow-checks = true # Enables runtime integer overflow checks. +# lto = false # Sets link-time optimization. +# panic = 'unwind' # The panic strategy. +# incremental = true # Incremental compilation. +# codegen-units = 16 # Number of code generation units. +# rpath = false # Sets the rpath linking option. +# [profile..build-override] # Overrides build-script settings. +# # Same keys for a normal profile. +# [profile..package.] # Override profile for a package. +# # Same keys for a normal profile (minus `panic`, `lto`, and `rpath`). + +# [registries.] # registries other than crates.io +# index = "…" # URL of the registry index +# token = "…" # authentication token for the registry + +# [registry] +# default = "…" # name of the default registry +# token = "…" # authentication token for crates.io + +# [source.] # source definition and replacement +# replace-with = "…" # replace this source with the given named source +# directory = "…" # path to a directory source +# registry = "…" # URL to a registry source +# local-registry = "…" # path to a local registry source +# git = "…" # URL of a git repository source +# branch = "…" # branch name for the git repository +# tag = "…" # tag name for the git repository +# rev = "…" # revision for the git repository + +# [target.] +# linker = "…" # linker to use +# runner = "…" # wrapper to run executables +# rustflags = ["…", "…"] # custom flags for `rustc` + +# [target.] +# runner = "…" # wrapper to run executables +# rustflags = ["…", "…"] # custom flags for `rustc` + +# [target..] # `links` build script override +# rustc-link-lib = ["foo"] +# rustc-link-search = ["/path/to/foo"] +# rustc-flags = ["-L", "/some/path"] +# rustc-cfg = ['key="value"'] +# rustc-env = {key = "value"} +# rustc-cdylib-link-arg = ["…"] +# metadata_key1 = "value" +# metadata_key2 = "value" + +# [term] +# quiet = false # whether cargo output is quiet +# verbose = false # whether cargo provides verbose output +# color = 'auto' # whether cargo colorizes output +# progress.when = 'auto' # whether cargo shows progress bar +# progress.width = 80 # width of progress bar diff --git a/.env b/.env new file mode 100644 index 000000000..39b245482 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017" +SOROBAN_RPC_URL="http://localhost:8000/soroban/rpc" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..0e881abf7 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,39 @@ +name: ContractClient + +on: + push: + branches: [master, release/**] + pull_request: + +jobs: + test: + name: test generated ContractClient + runs-on: ubuntu-22.04 + services: + rpc: + image: stellar/quickstart:soroban-dev@sha256:0ad51035cf7caba2fd99c7c1fad0945df6932be7d5c893e1520ccdef7d6a6ffe + ports: + - 8000:8000 + env: + ENABLE_LOGS: true + NETWORK: local + ENABLE_SOROBAN_RPC: true + options: >- + --health-cmd "curl --no-progress-meter --fail-with-body -X POST \"http://localhost:8000/soroban/rpc\" -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getNetwork\"}' && curl --no-progress-meter \"http://localhost:8000/friendbot\" | grep '\"invalid_field\": \"addr\"'" + --health-interval 10s + --health-timeout 5s + --health-retries 50 + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + # Workaround for some `yarn` nonsense, see: + # https://github.com/yarnpkg/yarn/issues/6312#issuecomment-429685210 + - run: yarn install --network-concurrency 1 + - run: yarn build:prod + - run: yarn test:e2e + diff --git a/.gitignore b/.gitignore index b25efdd09..7d07cee8f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ js-stellar-base/ test/unit/out/ .vscode/launch.json + +target +.soroban diff --git a/package.json b/package.json index 97810d48a..a264ed1fc 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,13 @@ "build:prod": "cross-env NODE_ENV=production yarn _build", "build:node": "yarn _babel && yarn build:ts", "build:ts": "tsc -p ./config/tsconfig.json", - "build:test": "tsc -p ./test/tsconfig.json", + "build:test": "tsc -p ./test/unit/tsconfig.json", "build:browser": "webpack -c config/webpack.config.browser.js", "build:docs": "cross-env NODE_ENV=docs yarn _babel", - "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/ jsdoc/", + "clean": "rm -rf lib/ dist/ coverage/ .nyc_output/ jsdoc/ test/e2e/.soroban test/e2e/contract-*.txt test/e2e/wasms/specs/*.json", "docs": "yarn build:docs && jsdoc -c ./config/.jsdoc.json --verbose", "test": "yarn build:test && yarn test:node && yarn test:integration && yarn test:browser", + "test:e2e": "./test/e2e/initialize.sh && ava", "test:node": "yarn _nyc mocha --recursive 'test/unit/**/*.js'", "test:integration": "yarn _nyc mocha --recursive 'test/integration/**/*.js'", "test:browser": "karma start config/karma.conf.js", @@ -76,6 +77,8 @@ ] }, "devDependencies": { + "ava": "^5.3.1", + "dotenv": "^16.3.1", "@babel/cli": "^7.23.0", "@babel/core": "^7.23.6", "@babel/eslint-parser": "^7.22.15", @@ -96,7 +99,6 @@ "@types/randombytes": "^2.0.1", "@types/sinon": "^17.0.2", "@types/urijs": "^1.19.20", - "@typescript-eslint/parser": "^6.14.0", "axios-mock-adapter": "^1.22.0", "babel-loader": "^9.1.3", "babel-plugin-istanbul": "^6.1.1", @@ -151,5 +153,13 @@ "randombytes": "^2.1.0", "toml": "^3.0.0", "urijs": "^1.19.1" + }, + "ava": { + "files": [ + "./test/e2e/src/test-*" + ], + "require": [ + "dotenv/config" + ] } } diff --git a/src/contract_spec.ts b/src/contract_spec.ts index 8921a886b..d7538e707 100644 --- a/src/contract_spec.ts +++ b/src/contract_spec.ts @@ -7,6 +7,12 @@ import { Contract, scValToBigInt, } from "."; +import { + AssembledTransaction, + ContractClient, + ContractClientOptions, + MethodOptions, +} from './soroban'; export interface Union { tag: string; @@ -163,7 +169,9 @@ export class ContractSpec { } let output = outputs[0]; if (output.switch().value === xdr.ScSpecType.scSpecTypeResult().value) { - return this.scValToNative(val, output.result().okType()); + return new AssembledTransaction.Result.Ok( + this.scValToNative(val, output.result().okType()) + ); } return this.scValToNative(val, output); } @@ -678,6 +686,60 @@ export class ContractSpec { return num; } + /** + * Gets the XDR error cases from the spec. + * + * @returns {xdr.ScSpecFunctionV0[]} all contract functions + * + */ + errorCases(): xdr.ScSpecUdtErrorEnumCaseV0[] { + return this.entries + .filter( + (entry) => + entry.switch().value === + xdr.ScSpecEntryKind.scSpecEntryUdtErrorEnumV0().value + ) + .flatMap((entry) => (entry.value() as xdr.ScSpecUdtErrorEnumV0).cases()); + } + + /** + * Generate a class from the contract spec that where each contract method gets included with a possibly-JSified name. + * + * Each method returns an AssembledTransaction object that can be used to sign and submit the transaction. + */ + generateContractClient(options: ContractClientOptions): ContractClient { + const spec = this; + let methods = this.funcs(); + const contractClient = new ContractClient(spec, options); + for (let method of methods) { + let name = method.name().toString(); + let jsName = toLowerCamelCase(name); + // @ts-ignore + contractClient[jsName] = async ( + args: Record, + options: MethodOptions + ) => { + return await AssembledTransaction.fromSimulation({ + method: name, + args: spec.funcArgsToScVals(name, args), + ...options, + ...contractClient.options, + errorTypes: spec + .errorCases() + .reduce( + (acc, curr) => ({ + ...acc, + [curr.value()]: { message: curr.doc().toString() }, + }), + {} as Pick + ), + parseResultXdr: (result: xdr.ScVal) => spec.funcResToNative(name, result), + }); + }; + } + return contractClient; + } + /** * Converts the contract spec to a JSON schema. * @@ -1138,3 +1200,23 @@ function enumToJsonSchema(udt: xdr.ScSpecUdtEnumV0): any { } return res; } + +/** + * converts a snake_case string to camelCase + */ +export function toLowerCamelCase(str: string): string { + return str.replace(/_\w/g, (m) => m[1].toUpperCase()); +} + +export type u32 = number; +export type i32 = number; +export type u64 = bigint; +export type i64 = bigint; +export type u128 = bigint; +export type i128 = bigint; +export type u256 = bigint; +export type i256 = bigint; +export type Option = T | undefined; +export type Typepoint = bigint; +export type Duration = bigint; + diff --git a/src/index.ts b/src/index.ts index 9cfea4015..ec315bd0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,7 @@ export * as Horizon from './horizon'; // Soroban RPC-related classes to expose export * as SorobanRpc from './soroban'; -export { ContractSpec } from './contract_spec'; +export * from './contract_spec'; // expose classes and functions from stellar-base export * from '@stellar/stellar-base'; diff --git a/src/soroban/assembled_transaction.ts b/src/soroban/assembled_transaction.ts new file mode 100644 index 000000000..c11cf2ebc --- /dev/null +++ b/src/soroban/assembled_transaction.ts @@ -0,0 +1,731 @@ +import type { ContractClientOptions, XDR_BASE64 } from "."; +import { + Account, + BASE_FEE, + Contract, + Operation, + SorobanRpc, + StrKey, + TimeoutInfinite, + TransactionBuilder, + authorizeEntry, + hash, + xdr, +} from ".."; +import type { Memo, MemoType, Transaction } from ".."; + +type Tx = Transaction, Operation[]>; + +type SendTx = SorobanRpc.Api.SendTransactionResponse; +type GetTx = SorobanRpc.Api.GetTransactionResponse; + +/** + * Error interface containing the error message + */ +interface ErrorMessage { + message: string; +} + +interface Result { + unwrap(): T; + unwrapErr(): E; + isOk(): boolean; + isErr(): boolean; +} + +class Ok implements Result { + constructor(readonly value: T) {} + unwrapErr(): never { throw new Error("No error") } + unwrap() { return this.value } + isOk() { return true } + isErr() { return false } +} + +class Err implements Result { + constructor(readonly error: E) {} + unwrapErr() { return this.error } + unwrap(): never { throw new Error(this.error.message) } + isOk() { return false } + isErr() { return true } +} + +export type MethodOptions = { + /** + * The fee to pay for the transaction. Default: BASE_FEE + */ + fee?: number; +}; + +export type AssembledTransactionOptions = MethodOptions & + ContractClientOptions & { + method: string; + args?: any[]; + parseResultXdr: (xdr: xdr.ScVal) => T; + }; + +export const NULL_ACCOUNT = + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; + +export class AssembledTransaction { + public raw?: Tx; + private simulation?: SorobanRpc.Api.SimulateTransactionResponse; + private simulationResult?: SorobanRpc.Api.SimulateHostFunctionResult; + private simulationTransactionData?: xdr.SorobanTransactionData; + private server: SorobanRpc.Server; + + static ExpiredStateError = class ExpiredStateError extends Error {} + static NeedsMoreSignaturesError = class NeedsMoreSignaturesError extends Error {} + static WalletDisconnectedError = class WalletDisconnectedError extends Error {} + static SendResultOnlyError = class SendResultOnlyError extends Error {} + static SendFailedError = class SendFailedError extends Error {} + static NoUnsignedNonInvokerAuthEntriesError = class NoUnsignedNonInvokerAuthEntriesError extends Error {} + + /** + * A minimal implementation of Rust's `Result` type. Used for contract methods that return Results, to maintain their distinction from methods that simply either return a value or throw. + */ + static Result = { + /** + * A minimal implementation of Rust's `Ok` Result type. Used for contract methods that return successful Results, to maintain their distinction from methods that simply either return a value or throw. + */ + Ok, + /** + * A minimal implementation of Rust's `Err` Result type. Used for contract methods that return unsuccessful Results, to maintain their distinction from methods that simply either return a value or throw. + */ + Err + } + + toJSON() { + return JSON.stringify({ + method: this.options.method, + tx: this.raw?.toXDR(), + simulationResult: { + auth: this.simulationData.result.auth.map((a) => a.toXDR("base64")), + retval: this.simulationData.result.retval.toXDR("base64"), + }, + simulationTransactionData: + this.simulationData.transactionData.toXDR("base64"), + }); + } + + static fromJSON( + options: Omit, "args">, + { + tx, + simulationResult, + simulationTransactionData, + }: { + tx: XDR_BASE64; + simulationResult: { + auth: XDR_BASE64[]; + retval: XDR_BASE64; + }; + simulationTransactionData: XDR_BASE64; + } + ): AssembledTransaction { + const txn = new AssembledTransaction(options); + txn.raw = TransactionBuilder.fromXDR(tx, options.networkPassphrase) as Tx; + txn.simulationResult = { + auth: simulationResult.auth.map((a) => + xdr.SorobanAuthorizationEntry.fromXDR(a, "base64") + ), + retval: xdr.ScVal.fromXDR(simulationResult.retval, "base64"), + }; + txn.simulationTransactionData = xdr.SorobanTransactionData.fromXDR( + simulationTransactionData, + "base64" + ); + return txn; + } + + private constructor(public options: AssembledTransactionOptions) { + this.server = new SorobanRpc.Server(this.options.rpcUrl, { + allowHttp: this.options.rpcUrl.startsWith("http://"), + }); + } + + static async fromSimulation( + options: AssembledTransactionOptions + ): Promise> { + const tx = new AssembledTransaction(options); + const contract = new Contract(options.contractId); + + tx.raw = new TransactionBuilder(await tx.getAccount(), { + fee: options.fee?.toString(10) ?? BASE_FEE, + networkPassphrase: options.networkPassphrase, + }) + .addOperation(contract.call(options.method, ...(options.args ?? []))) + .setTimeout(TimeoutInfinite) + .build(); + + return await tx.simulate(); + } + + simulate = async (): Promise => { + if (!this.raw) throw new Error("Transaction has not yet been assembled"); + this.simulation = await this.server.simulateTransaction(this.raw); + + if (SorobanRpc.Api.isSimulationSuccess(this.simulation)) { + this.raw = SorobanRpc.assembleTransaction( + this.raw, + this.simulation + ).build(); + } + + return this; + }; + + get simulationData(): { + result: SorobanRpc.Api.SimulateHostFunctionResult; + transactionData: xdr.SorobanTransactionData; + } { + if (this.simulationResult && this.simulationTransactionData) { + return { + result: this.simulationResult, + transactionData: this.simulationTransactionData, + }; + } + // else, we know we just did the simulation on this machine + const simulation = this.simulation!; + if (SorobanRpc.Api.isSimulationError(simulation)) { + throw new Error(`Transaction simulation failed: "${simulation.error}"`); + } + + if (SorobanRpc.Api.isSimulationRestore(simulation)) { + throw new AssembledTransaction.ExpiredStateError( + `You need to restore some contract state before you can invoke this method. ${JSON.stringify( + simulation, + null, + 2 + )}` + ); + } + + if (!simulation.result) { + throw new Error( + `Expected an invocation simulation, but got no 'result' field. Simulation: ${JSON.stringify( + simulation, + null, + 2 + )}` + ); + } + + // add to object for serialization & deserialization + this.simulationResult = simulation.result; + this.simulationTransactionData = simulation.transactionData.build(); + + return { + result: this.simulationResult, + transactionData: this.simulationTransactionData!, + }; + } + + get result(): T { + try { + return this.options.parseResultXdr(this.simulationData.result.retval); + } catch (e) { + if (!implementsToString(e)) throw e; + let err = this.parseError(e.toString()); + if (err) return err as T; + throw e; + } + } + + parseError(errorMessage: string): Result | undefined { + if (!this.options.errorTypes) return undefined; + const match = errorMessage.match(contractErrorPattern); + if (!match) return undefined; + let i = parseInt(match[1], 10); + let err = this.options.errorTypes[i]; + if (!err) return undefined; + return new AssembledTransaction.Result.Err(err); + } + + getPublicKey = async (): Promise => { + const wallet = this.options.wallet; + if (!(await wallet.isConnected()) || !(await wallet.isAllowed())) { + return undefined; + } + return (await wallet.getUserInfo()).publicKey; + }; + + /** + * Get account details from the Soroban network for the publicKey currently + * selected in user's wallet. If not connected to Freighter, use placeholder + * null account. + */ + getAccount = async (): Promise => { + const publicKey = await this.getPublicKey(); + return publicKey + ? await this.server.getAccount(publicKey) + : new Account(NULL_ACCOUNT, "0"); + }; + + /** + * Sign the transaction with the `wallet` (default Freighter), then send to + * the network and return a `SentTransaction` that keeps track of all the + * attempts to send and fetch the transaction from the network. + */ + signAndSend = async ({ + secondsToWait = 10, + force = false, + }: { + /** + * Wait `secondsToWait` seconds (default: 10) for both the transaction to SEND successfully (will keep trying if the server returns `TRY_AGAIN_LATER`), as well as for the transaction to COMPLETE (will keep checking if the server returns `PENDING`). + */ + secondsToWait?: number; + /** + * If `true`, sign and send the transaction even if it is a read call. + */ + force?: boolean; + } = {}): Promise> => { + if (!this.raw) { + throw new Error("Transaction has not yet been simulated"); + } + + if (!force && this.isReadCall) { + throw new Error( + "This is a read call. It requires no signature or sending. Use `force: true` to sign and send anyway." + ); + } + + if (!(await this.hasRealInvoker())) { + throw new AssembledTransaction.WalletDisconnectedError("Wallet is not connected"); + } + + if (this.raw.source !== (await this.getAccount()).accountId()) { + throw new Error( + `You must submit the transaction with the account that originally created it. Please switch to the wallet with "${this.raw.source}" as its public key.` + ); + } + + if ((await this.needsNonInvokerSigningBy()).length) { + throw new AssembledTransaction.NeedsMoreSignaturesError( + "Transaction requires more signatures. See `needsNonInvokerSigningBy` for details." + ); + } + + return await SentTransaction.init(this.options, this, secondsToWait); + }; + + getStorageExpiration = async () => { + const entryRes = await this.server.getLedgerEntries( + new Contract(this.options.contractId).getFootprint() + ); + if ( + !entryRes.entries || + !entryRes.entries.length || + !entryRes.entries[0].liveUntilLedgerSeq + ) + throw new Error("failed to get ledger entry"); + return entryRes.entries[0].liveUntilLedgerSeq; + }; + + /** + * Get a list of accounts, other than the invoker of the simulation, that + * need to sign auth entries in this transaction. + * + * Soroban allows multiple people to sign a transaction. Someone needs to + * sign the final transaction envelope; this person/account is called the + * _invoker_, or _source_. Other accounts might need to sign individual auth + * entries in the transaction, if they're not also the invoker. + * + * This function returns a list of accounts that need to sign auth entries, + * assuming that the same invoker/source account will sign the final + * transaction envelope as signed the initial simulation. + * + * One at a time, for each public key in this array, you will need to + * serialize this transaction with `toJSON`, send to the owner of that key, + * deserialize the transaction with `txFromJson`, and call + * {@link signAuthEntries}. Then re-serialize and send to the next account + * in this list. + */ + needsNonInvokerSigningBy = async ({ + includeAlreadySigned = false, + }: { + /** + * Whether or not to include auth entries that have already been signed. Default: false + */ + includeAlreadySigned?: boolean; + } = {}): Promise => { + if (!this.raw) { + throw new Error("Transaction has not yet been simulated"); + } + + // We expect that any transaction constructed by these libraries has a + // single operation, which is an InvokeHostFunction operation. The host + // function being invoked is the contract method call. + if (!("operations" in this.raw)) { + throw new Error( + `Unexpected Transaction type; no operations: ${JSON.stringify( + this.raw + )}` + ); + } + const rawInvokeHostFunctionOp = this.raw + .operations[0] as Operation.InvokeHostFunction; + + return [ + ...new Set( + (rawInvokeHostFunctionOp.auth ?? []) + .filter( + (entry) => + entry.credentials().switch() === + xdr.SorobanCredentialsType.sorobanCredentialsAddress() && + (includeAlreadySigned || + entry.credentials().address().signature().switch().name === + "scvVoid") + ) + .map((entry) => + StrKey.encodeEd25519PublicKey( + entry.credentials().address().address().accountId().ed25519() + ) + ) + ), + ]; + }; + + preImageFor( + entry: xdr.SorobanAuthorizationEntry, + signatureExpirationLedger: number + ): xdr.HashIdPreimage { + const addrAuth = entry.credentials().address(); + return xdr.HashIdPreimage.envelopeTypeSorobanAuthorization( + new xdr.HashIdPreimageSorobanAuthorization({ + networkId: hash(Buffer.from(this.options.networkPassphrase)), + nonce: addrAuth.nonce(), + invocation: entry.rootInvocation(), + signatureExpirationLedger, + }) + ); + } + + /** + * If {@link needsNonInvokerSigningBy} returns a non-empty list, you can serialize + * the transaction with `toJSON`, send it to the owner of one of the public keys + * in the map, deserialize with `txFromJSON`, and call this method on their + * machine. Internally, this will use `signAuthEntry` function from connected + * `wallet` for each. + * + * Then, re-serialize the transaction and either send to the next + * `needsNonInvokerSigningBy` owner, or send it back to the original account + * who simulated the transaction so they can {@link sign} the transaction + * envelope and {@link send} it to the network. + * + * Sending to all `needsNonInvokerSigningBy` owners in parallel is not currently + * supported! + */ + signAuthEntries = async ( + /** + * When to set each auth entry to expire. Could be any number of blocks in + * the future. Can be supplied as a promise or a raw number. Default: + * contract's current `persistent` storage expiration date/ledger + * number/block. + */ + expiration: number | Promise = this.getStorageExpiration() + ): Promise => { + if (!this.raw) + throw new Error("Transaction has not yet been assembled or simulated"); + const needsNonInvokerSigningBy = await this.needsNonInvokerSigningBy(); + + if (!needsNonInvokerSigningBy) + throw new AssembledTransaction.NoUnsignedNonInvokerAuthEntriesError( + "No unsigned non-invoker auth entries; maybe you already signed?" + ); + const publicKey = await this.getPublicKey(); + if (!publicKey) + throw new Error( + "Could not get public key from wallet; maybe not signed in?" + ); + if (needsNonInvokerSigningBy.indexOf(publicKey) === -1) + throw new Error(`No auth entries for public key "${publicKey}"`); + const wallet = this.options.wallet; + + const rawInvokeHostFunctionOp = this.raw + .operations[0] as Operation.InvokeHostFunction; + + const authEntries = rawInvokeHostFunctionOp.auth ?? []; + + for (const [i, entry] of authEntries.entries()) { + if ( + entry.credentials().switch() !== + xdr.SorobanCredentialsType.sorobanCredentialsAddress() + ) { + // if the invoker/source account, then the entry doesn't need explicit + // signature, since the tx envelope is already signed by the source + // account, so only check for sorobanCredentialsAddress + continue; + } + const pk = StrKey.encodeEd25519PublicKey( + entry.credentials().address().address().accountId().ed25519() + ); + + // this auth entry needs to be signed by a different account + // (or maybe already was!) + if (pk !== publicKey) continue; + + authEntries[i] = await authorizeEntry( + entry, + async (preimage) => + Buffer.from( + await wallet.signAuthEntry(preimage.toXDR("base64")), + "base64" + ), + await expiration, + this.options.networkPassphrase + ); + } + }; + + get isReadCall(): boolean { + const authsCount = this.simulationData.result.auth.length; + const writeLength = this.simulationData.transactionData + .resources() + .footprint() + .readWrite().length; + return authsCount === 0 && writeLength === 0; + } + + hasRealInvoker = async (): Promise => { + const account = await this.getAccount(); + return account.accountId() !== NULL_ACCOUNT; + }; +} + +/** + * A transaction that has been sent to the Soroban network. This happens in two steps: + * + * 1. `sendTransaction`: initial submission of the transaction to the network. + * This step can run into problems, and will be retried with exponential + * backoff if it does. See all attempts in `sendTransactionResponseAll` and the + * most recent attempt in `sendTransactionResponse`. + * 2. `getTransaction`: once the transaction has been submitted to the network + * successfully, you need to wait for it to finalize to get the results of the + * transaction. This step can also run into problems, and will be retried with + * exponential backoff if it does. See all attempts in + * `getTransactionResponseAll` and the most recent attempt in + * `getTransactionResponse`. + */ +class SentTransaction { + public server: SorobanRpc.Server; + public signed?: Tx; + public sendTransactionResponse?: SendTx; + public sendTransactionResponseAll?: SendTx[]; + public getTransactionResponse?: GetTx; + public getTransactionResponseAll?: GetTx[]; + + constructor( + public options: AssembledTransactionOptions, + public assembled: AssembledTransaction + ) { + this.server = new SorobanRpc.Server(this.options.rpcUrl, { + allowHttp: this.options.rpcUrl.startsWith("http://"), + }); + this.assembled = assembled; + } + + static init = async ( + options: AssembledTransactionOptions, + assembled: AssembledTransaction, + secondsToWait: number = 10 + ): Promise> => { + const tx = new SentTransaction(options, assembled); + return await tx.send(secondsToWait); + }; + + private send = async (secondsToWait: number = 10): Promise => { + const wallet = this.assembled.options.wallet; + + this.sendTransactionResponseAll = await withExponentialBackoff( + async (previousFailure) => { + if (previousFailure) { + // Increment transaction sequence number and resimulate before trying again + + // Soroban transaction can only have 1 operation + const op = this.assembled.raw! + .operations[0] as Operation.InvokeHostFunction; + + this.assembled.raw = new TransactionBuilder( + await this.assembled.getAccount(), + { + fee: this.assembled.raw!.fee, + networkPassphrase: this.options.networkPassphrase, + } + ) + .setTimeout(TimeoutInfinite) + .addOperation( + Operation.invokeHostFunction({ ...op, auth: op.auth ?? [] }) + ) + .build(); + + await this.assembled.simulate(); + } + + const signature = await wallet.signTransaction( + this.assembled.raw!.toXDR(), + { + networkPassphrase: this.options.networkPassphrase, + } + ); + + this.signed = TransactionBuilder.fromXDR( + signature, + this.options.networkPassphrase + ) as Tx; + + return this.server.sendTransaction(this.signed); + }, + (resp) => resp.status !== "PENDING", + secondsToWait + ); + + this.sendTransactionResponse = + this.sendTransactionResponseAll[ + this.sendTransactionResponseAll.length - 1 + ]; + + if (this.sendTransactionResponse.status !== "PENDING") { + throw new Error( + `Tried to resubmit transaction for ${secondsToWait} seconds, but it's still failing. ` + + `All attempts: ${JSON.stringify( + this.sendTransactionResponseAll, + null, + 2 + )}` + ); + } + + const { hash } = this.sendTransactionResponse; + + this.getTransactionResponseAll = await withExponentialBackoff( + () => this.server.getTransaction(hash), + (resp) => resp.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND, + secondsToWait + ); + + this.getTransactionResponse = + this.getTransactionResponseAll[this.getTransactionResponseAll.length - 1]; + if ( + this.getTransactionResponse.status === + SorobanRpc.Api.GetTransactionStatus.NOT_FOUND + ) { + console.error( + `Waited ${secondsToWait} seconds for transaction to complete, but it did not. ` + + `Returning anyway. Check the transaction status manually. ` + + `Sent transaction: ${JSON.stringify( + this.sendTransactionResponse, + null, + 2 + )}\n` + + `All attempts to get the result: ${JSON.stringify( + this.getTransactionResponseAll, + null, + 2 + )}` + ); + } + + return this; + }; + + get result(): T { + // 1. check if transaction was submitted and awaited with `getTransaction` + if ("getTransactionResponse" in this && this.getTransactionResponse) { + // getTransactionResponse has a `returnValue` field unless it failed + if ("returnValue" in this.getTransactionResponse) { + return this.options.parseResultXdr( + this.getTransactionResponse.returnValue! + ); + } + + // if "returnValue" not present, the transaction failed; return without parsing the result + throw new Error("Transaction failed! Cannot parse result."); + } + + // 2. otherwise, maybe it was merely sent with `sendTransaction` + if (this.sendTransactionResponse) { + const errorResult = this.sendTransactionResponse.errorResult?.result(); + if (errorResult) { + throw new AssembledTransaction.SendFailedError( + `Transaction simulation looked correct, but attempting to send the transaction failed. Check \`simulation\` and \`sendTransactionResponseAll\` to troubleshoot. Decoded \`sendTransactionResponse.errorResultXdr\`: ${errorResult}` + ); + } + throw new AssembledTransaction.SendResultOnlyError( + `Transaction was sent to the network, but not yet awaited. No result to show. Await transaction completion with \`getTransaction(sendTransactionResponse.hash)\`` + ); + } + + // 3. finally, if neither of those are present, throw an error + throw new Error( + `Sending transaction failed: ${JSON.stringify(this.assembled)}` + ); + } +} + +/** + * Keep calling a `fn` for `secondsToWait` seconds, if `keepWaitingIf` is true. + * Returns an array of all attempts to call the function. + */ +async function withExponentialBackoff( + fn: (previousFailure?: T) => Promise, + keepWaitingIf: (result: T) => boolean, + secondsToWait: number, + exponentialFactor = 1.5, + verbose = false +): Promise { + const attempts: T[] = []; + + let count = 0; + attempts.push(await fn()); + if (!keepWaitingIf(attempts[attempts.length - 1])) return attempts; + + const waitUntil = new Date(Date.now() + secondsToWait * 1000).valueOf(); + let waitTime = 1000; + let totalWaitTime = waitTime; + + while ( + Date.now() < waitUntil && + keepWaitingIf(attempts[attempts.length - 1]) + ) { + count++; + // Wait a beat + if (verbose) { + console.info( + `Waiting ${waitTime}ms before trying again (bringing the total wait time to ${totalWaitTime}ms so far, of total ${ + secondsToWait * 1000 + }ms)` + ); + } + await new Promise((res) => setTimeout(res, waitTime)); + // Exponential backoff + waitTime = waitTime * exponentialFactor; + if (new Date(Date.now() + waitTime).valueOf() > waitUntil) { + waitTime = waitUntil - Date.now(); + if (verbose) { + console.info(`was gonna wait too long; new waitTime: ${waitTime}ms`); + } + } + totalWaitTime = waitTime + totalWaitTime; + // Try again + attempts.push(await fn(attempts[attempts.length - 1])); + if (verbose && keepWaitingIf(attempts[attempts.length - 1])) { + console.info( + `${count}. Called ${fn}; ${ + attempts.length + } prev attempts. Most recent: ${JSON.stringify( + attempts[attempts.length - 1], + null, + 2 + )}` + ); + } + } + + return attempts; +} + +const contractErrorPattern = /Error\(Contract, #(\d+)\)/; + +function implementsToString(obj: unknown): obj is { toString(): string } { + return typeof obj === "object" && obj !== null && "toString" in obj; +} diff --git a/src/soroban/contract_client.ts b/src/soroban/contract_client.ts new file mode 100644 index 000000000..ea1b95479 --- /dev/null +++ b/src/soroban/contract_client.ts @@ -0,0 +1,65 @@ +import { AssembledTransaction } from '.' +import { ContractSpec, xdr } from '..' + +export type XDR_BASE64 = string; + +export interface Wallet { + isConnected: () => Promise; + isAllowed: () => Promise; + getUserInfo: () => Promise<{ publicKey?: string }>; + signTransaction: ( + tx: XDR_BASE64, + opts?: { + network?: string; + networkPassphrase?: string; + accountToSign?: string; + } + ) => Promise; + signAuthEntry: ( + entryXdr: XDR_BASE64, + opts?: { + accountToSign?: string; + } + ) => Promise; +} + + +export type ContractClientOptions = { + contractId: string; + networkPassphrase: string; + rpcUrl: string; + errorTypes?: Record; + /** + * A Wallet interface, such as Freighter, that has the methods `isConnected`, `isAllowed`, `getUserInfo`, and `signTransaction`. If not provided, will attempt to import and use Freighter. Example: + * + * @example + * ```ts + * import freighter from "@stellar/freighter-api"; + * import { Contract } from "test_custom_types"; + * const contract = new Contract({ + * …, + * wallet: freighter, + * }) + * ``` + */ + wallet: Wallet; +}; + +export class ContractClient { + constructor( + public readonly spec: ContractSpec, + public readonly options: ContractClientOptions, + ) {} + + txFromJSON = (json: string): AssembledTransaction => { + const { method, ...tx } = JSON.parse(json) + return AssembledTransaction.fromJSON( + { + ...this.options, + method, + parseResultXdr: (result: xdr.ScVal) => this.spec.funcResToNative(method, result), + }, + tx, + ); + } +} diff --git a/src/soroban/index.ts b/src/soroban/index.ts index 36e296ee1..c269a61a3 100644 --- a/src/soroban/index.ts +++ b/src/soroban/index.ts @@ -9,5 +9,7 @@ export { Server, Durability } from './server'; export { default as AxiosClient } from './axios'; export { parseRawSimulation, parseRawEvents } from './parsers'; export * from './transaction'; +export * from './contract_client'; +export * from './assembled_transaction'; export default module.exports; diff --git a/test/e2e/.gitignore b/test/e2e/.gitignore new file mode 100644 index 000000000..d8dea8c98 --- /dev/null +++ b/test/e2e/.gitignore @@ -0,0 +1,6 @@ +build +node_modules +yarn.lock +contract-*.txt +.soroban +wasms/specs/*.json diff --git a/test/e2e/.nvmrc b/test/e2e/.nvmrc new file mode 100644 index 000000000..03db17dcc --- /dev/null +++ b/test/e2e/.nvmrc @@ -0,0 +1 @@ +v20.6.0 diff --git a/test/e2e/initialize.sh b/test/e2e/initialize.sh new file mode 100755 index 000000000..0ab8a545f --- /dev/null +++ b/test/e2e/initialize.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# read .env file, but prefer explicitly set environment variables +IFS=$'\n' +for l in $(cat .env); do + IFS='=' read -ra VARVAL <<< "$l" + # If variable with such name already exists, preserves its value + eval "export ${VARVAL[0]}=\${${VARVAL[0]}:-${VARVAL[1]}}" +done +unset IFS + +# a good-enough implementation of __dirname from https://blog.daveeddy.com/2015/04/13/dirname-case-study-for-bash-and-node/ +dirname="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "###################### Initializing e2e tests ########################" + +soroban="$dirname/../../target/bin/soroban" +if [[ -f "$soroban" ]]; then + echo "Using soroban binary from ./target/bin" +else + echo "Building pinned soroban binary" + (cd "$dirname/../.." && cargo install_soroban) +fi + +NETWORK_STATUS=$(curl -s -X POST "$SOROBAN_RPC_URL" -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 8675309, "method": "getHealth" }' | sed 's/.*"status":"\(.*\)".*/\1/') + +echo Network +echo " RPC: $SOROBAN_RPC_URL" +echo " Passphrase: \"$SOROBAN_NETWORK_PASSPHRASE\"" +echo " Status: $NETWORK_STATUS" + +if [[ "$NETWORK_STATUS" != "healthy" ]]; then + echo "Network is not healthy (not running?), exiting" + exit 1 +fi + +# Print command before executing, from https://stackoverflow.com/a/23342259/249801 +# Discussion: https://github.com/stellar/soroban-tools/pull/1034#pullrequestreview-1690667116 +exe() { echo"${@/eval/}" ; "$@" ; } + +function fund_all() { + exe eval "$soroban config identity fund" + exe eval "$soroban config identity generate alice" + exe eval "$soroban config identity fund alice" + exe eval "$soroban config identity generate bob" + exe eval "$soroban config identity fund bob" +} +function upload() { + exe eval "($soroban contract $1 --wasm $dirname/$2 --ignore-checks) > $dirname/$3" +} +function deploy() { + exe eval "($soroban contract deploy --wasm-hash $(cat $dirname/$1) --ignore-checks) > $dirname/$2" +} +function deploy_all() { + upload deploy wasms/test_custom_types.wasm contract-id-custom-types.txt + upload deploy wasms/test_hello_world.wasm contract-id-hello-world.txt + upload deploy wasms/test_swap.wasm contract-id-swap.txt + upload install wasms/test_token.wasm contract-token-hash.txt + deploy contract-token-hash.txt contract-id-token-a.txt + deploy contract-token-hash.txt contract-id-token-b.txt +} +function initialize() { + exe eval "$soroban contract invoke --id $(cat $dirname/$1) -- initialize --admin $($soroban config identity address) --decimal 0 --name 'Token $2' --symbol '$2'" +} +function initialize_all() { + initialize contract-id-token-a.txt A + initialize contract-id-token-b.txt B +} +function generate_spec() { + exe eval "$soroban contract inspect --wasm $dirname/wasms/$1.wasm --output xdr-base64-array > $dirname/wasms/specs/$1.json" +} +function generate_spec_all() { + generate_spec test_custom_types + generate_spec test_hello_world + generate_spec test_swap + generate_spec test_token +} +function mint() { + exe eval "$soroban contract invoke --id $(cat $dirname/$1) -- mint --amount 2000000 --to $($soroban config identity address $2)" +} +function mint_all() { + mint contract-id-token-a.txt alice + mint contract-id-token-b.txt bob +} +function check_specs() { + ls -l $dirname/wasms/specs/*.json 2> /dev/null | wc -l | xargs +} +function check_balance() { + ls -l $dirname/contract-id-*.txt 2> /dev/null | wc -l | xargs + if [ $? -eq 0 ]; then + return 0 + fi + $soroban contract invoke --id $(cat $dirname/contract-id-token-$1.txt) -- balance --id $($soroban config identity address $2) | xargs +} + +if [ $(check_specs) == 4 ]; then + ALICE_BALANCE=$(check_balance a alice) + + if [ $ALICE_BALANCE -gt 0 ]; then + echo "Skipping initialization" + echo " specs generated: $(check_specs)" + echo " alice token A balance: $ALICE_BALANCE" + echo "To re-initialize, delete generated files with 'yarn clean'" + exit 0 + fi +fi + +fund_all +deploy_all +initialize_all +mint_all +generate_spec_all diff --git a/test/e2e/src/test-custom-types.js b/test/e2e/src/test-custom-types.js new file mode 100644 index 000000000..9f2217cb6 --- /dev/null +++ b/test/e2e/src/test-custom-types.js @@ -0,0 +1,194 @@ +const test = require('ava') +const fs = require('node:fs') +const { ContractSpec, SorobanRpc } = require('../../..') +const { root, wallet, rpcUrl, networkPassphrase } = require('./util') +const xdr = require('../wasms/specs/test_custom_types.json') + +const spec = new ContractSpec(xdr) +const contractId = fs.readFileSync(`${__dirname}/../contract-id-custom-types.txt`, "utf8").trim() +const contract = spec.generateContractClient({ + networkPassphrase, + contractId, + rpcUrl, + wallet, +}); + +const addr = root.address; +const publicKey = root.keypair.publicKey(); + +test('hello', async t => { + const { result } = await contract.hello({ hello: 'tests' }) + t.is(result, 'tests') +}) + +test('woid', async t => { + t.is((await contract.woid()).result, null) +}) + +test('u32_fail_on_even', async t => { + t.deepEqual( + (await contract.u32FailOnEven({ u32_: 1 })).result, + new SorobanRpc.AssembledTransaction.Result.Ok(1) + ) + t.deepEqual( + (await contract.u32FailOnEven({ u32_: 2 })).result, + new SorobanRpc.AssembledTransaction.Result.Err({ message: "Please provide an odd number" }) + ) +}) + +test('u32', async t => { + t.is((await contract.u32_({ u32_: 1 })).result, 1) // eslint-disable-line no-underscore-dangle +}) + +test('i32', async t => { + t.is((await contract.i32_({ i32_: 1 })).result, 1) // eslint-disable-line no-underscore-dangle +}) + +test('i64', async t => { + t.is((await contract.i64_({ i64_: 1n })).result, 1n) // eslint-disable-line no-underscore-dangle +}) + +test("strukt_hel", async (t) => { + const strukt = { a: 0, b: true, c: "world" } + t.deepEqual((await contract.struktHel({ strukt })).result, ["Hello", "world"]) +}) + +test("strukt", async (t) => { + const strukt = { a: 0, b: true, c: "hello" } + t.deepEqual((await contract.strukt({ strukt })).result, strukt) +}) + +test('simple first', async t => { + const simple = { tag: 'First', values: undefined } + const ret = { tag: 'First' } + t.deepEqual((await contract.simple({ simple })).result, ret) +}) + +test('simple second', async t => { + const simple = { tag: 'Second', values: undefined } + const ret = { tag: 'Second' } + t.deepEqual((await contract.simple({ simple })).result, ret) +}) + +test('simple third', async t => { + const simple = { tag: 'Third', values: undefined } + const ret = { tag: 'Third' } + t.deepEqual((await contract.simple({ simple })).result, ret) +}) + +test('complex with struct', async t => { + const arg = { tag: 'Struct', values: [{ a: 0, b: true, c: 'hello' }] } + t.deepEqual((await contract.complex({ complex: arg })).result, arg) +}) + +test('complex with tuple', async t => { + const arg = { tag: 'Tuple', values: [[{ a: 0, b: true, c: 'hello' }, { tag: 'First', values: undefined }]] } + const ret = { tag: 'Tuple', values: [[{ a: 0, b: true, c: 'hello' }, { tag: 'First' }]] } + t.deepEqual((await contract.complex({ complex: arg })).result, ret) +}) + +test('complex with enum', async t => { + const arg = { tag: 'Enum', values: [{ tag: 'First', values: undefined }] } + const ret = { tag: 'Enum', values: [{ tag: 'First' }] } + t.deepEqual((await contract.complex({ complex: arg })).result, ret) +}) + +test('complex with asset', async t => { + const arg = { tag: 'Asset', values: [publicKey, 1n] } + t.deepEqual((await contract.complex({ complex: arg })).result, arg) +}) + +test('complex with void', async t => { + const complex = { tag: 'Void', values: undefined } + const ret = { tag: 'Void' } + t.deepEqual((await contract.complex({ complex })).result, ret) +}) + +test('addresse', async t => { + t.deepEqual((await contract.addresse({ addresse: publicKey })).result, addr.toString()) +}) + +test('bytes', async t => { + const bytes = Buffer.from('hello') + t.deepEqual((await contract.bytes({ bytes })).result, bytes) +}) + +test('bytesN', async t => { + const bytesN = Buffer.from('123456789') // what's the correct way to construct bytesN? + t.deepEqual((await contract.bytesN({ bytes_n: bytesN })).result, bytesN) +}) + +test('card', async t => { + const card = 11 + t.is((await contract.card({ card })).result, card) +}) + +test('boolean', async t => { + t.is((await contract.boolean({ boolean: true })).result, true) +}) + +test('not', async t => { + t.is((await contract.not({ boolean: true })).result, false) +}) + +test('i128', async t => { + t.is((await contract.i128({ i128: -1n })).result, -1n) +}) + +test('u128', async t => { + t.is((await contract.u128({ u128: 1n })).result, 1n) +}) + +test('multi_args', async t => { + t.is((await contract.multiArgs({ a: 1, b: true })).result, 1) + t.is((await contract.multiArgs({ a: 1, b: false })).result, 0) +}) + +test('map', async t => { + const map = new Map() + map.set(1, true) + map.set(2, false) + // map.set(3, 'hahaha') // should throw an error + t.deepEqual((await contract.map({ map })).result, Array.from(map.entries())) +}) + +test('vec', async t => { + const vec = [1, 2, 3] + t.deepEqual((await contract.vec({ vec })).result, vec) +}) + +test('tuple', async t => { + const tuple = ['hello', 1] + t.deepEqual((await contract.tuple({ tuple })).result, tuple) +}) + +test('option', async t => { + // this makes sense + t.deepEqual((await contract.option({ option: 1 })).result, 1) + + // this passes but shouldn't + t.deepEqual((await contract.option({ option: undefined })).result, undefined) + + // this is the behavior we probably want, but fails + // t.deepEqual(await contract.option(), undefined) // typing and implementation require the object + // t.deepEqual((await contract.option({})).result, undefined) // typing requires argument; implementation would be fine with this + // t.deepEqual((await contract.option({ option: undefined })).result, undefined) +}) + +test('u256', async t => { + t.is((await contract.u256({ u256: 1n })).result, 1n) +}) + +test('i256', async t => { + t.is((await contract.i256({ i256: -1n })).result, -1n) +}) + +test('string', async t => { + t.is((await contract.string({ string: 'hello' })).result, 'hello') +}) + +test('tuple_strukt', async t => { + const arg = [{ a: 0, b: true, c: 'hello' }, { tag: 'First', values: undefined }] + const res = [{ a: 0, b: true, c: 'hello' }, { tag: 'First' }] + t.deepEqual((await contract.tupleStrukt({ tuple_strukt: arg })).result, res) +}) diff --git a/test/e2e/src/test-hello-world.js b/test/e2e/src/test-hello-world.js new file mode 100644 index 000000000..eb60f675c --- /dev/null +++ b/test/e2e/src/test-hello-world.js @@ -0,0 +1,35 @@ +const test = require('ava') +const fs = require('node:fs') +const { ContractSpec } = require('../../..') +const { root, wallet, rpcUrl, networkPassphrase } = require('./util') +const xdr = require('../wasms/specs/test_hello_world.json') + +const spec = new ContractSpec(xdr) +const contractId = fs.readFileSync(`${__dirname}/../contract-id-hello-world.txt`, "utf8").trim() +const contract = spec.generateContractClient({ + networkPassphrase, + contractId, + rpcUrl, + wallet, +}); + +test("hello", async (t) => { + t.deepEqual((await contract.hello({ world: "tests" })).result, ["Hello", "tests"]); +}); + +test("auth", async (t) => { + t.deepEqual( + (await contract.auth({ + addr: root.keypair.publicKey(), + world: 'lol' + })).result, + root.address.toString() + ) +}); + +test("inc", async (t) => { + const { result: startingBalance } = await contract.getCount() + const inc = await contract.inc() + t.is((await inc.signAndSend()).result, startingBalance + 1) + t.is((await contract.getCount()).result, startingBalance + 1) +}); diff --git a/test/e2e/src/test-methods-as-args.js b/test/e2e/src/test-methods-as-args.js new file mode 100644 index 000000000..a37f226a7 --- /dev/null +++ b/test/e2e/src/test-methods-as-args.js @@ -0,0 +1,21 @@ +const test = require('ava') +const fs = require('node:fs') +const { ContractSpec } = require('../../..') +const { wallet, rpcUrl, networkPassphrase } = require('./util') +const xdr = require('../wasms/specs/test_hello_world.json') + +const spec = new ContractSpec(xdr) +const contractId = fs.readFileSync(`${__dirname}/../contract-id-hello-world.txt`, "utf8").trim() +const contract = spec.generateContractClient({ + networkPassphrase, + contractId, + rpcUrl, + wallet, +}); + +// this test checks that apps can pass methods as arguments to other methods and have them still work +const { hello } = contract + +test("hello", async (t) => { + t.deepEqual((await hello({ world: "tests" })).result, ["Hello", "tests"]); +}); diff --git a/test/e2e/src/test-swap.js b/test/e2e/src/test-swap.js new file mode 100644 index 000000000..8e66076e2 --- /dev/null +++ b/test/e2e/src/test-swap.js @@ -0,0 +1,137 @@ +const test = require('ava') +const fs = require('node:fs') +const { SorobanRpc, ContractSpec } = require('../../..') +const { wallet, rpcUrl, alice, bob, networkPassphrase, root, Wallet } = require('./util') +const swapXdr = require('../wasms/specs/test_swap.json') +const tokenXdr = require('../wasms/specs/test_token.json') + +const swapSpec = new ContractSpec(swapXdr) +const tokenSpec = new ContractSpec(tokenXdr) + +const swapId = fs.readFileSync(`${__dirname}/../contract-id-swap.txt`, "utf8").trim() +const tokenAId = fs.readFileSync(`${__dirname}/../contract-id-token-a.txt`, "utf8").trim() +const tokenBId = fs.readFileSync(`${__dirname}/../contract-id-token-b.txt`, "utf8").trim() + +// `root` is the invoker of all contracts +const tokenA = tokenSpec.generateContractClient({ + contractId: tokenAId, + networkPassphrase, + rpcUrl, + wallet, +}) +const tokenB = tokenSpec.generateContractClient({ + contractId: tokenBId, + networkPassphrase, + rpcUrl, + wallet, +}) +function swapContractAs(invoker) { + return swapSpec.generateContractClient({ + contractId: swapId, + networkPassphrase, + rpcUrl, + wallet: new Wallet(invoker.keypair.publicKey()), + }) +} + +const amountAToSwap = 2n +const amountBToSwap = 1n +const alicePk = alice.keypair.publicKey() +const bobPk = bob.keypair.publicKey() + +test('calling `signAndSend()` too soon throws descriptive error', async t => { + const swapContract = swapContractAs(root) + const tx = await swapContract.swap({ + a: alicePk, + b: bobPk, + token_a: tokenAId, + token_b: tokenBId, + amount_a: amountAToSwap, + min_a_for_b: amountAToSwap, + amount_b: amountBToSwap, + min_b_for_a: amountBToSwap, + }) + const error = await t.throwsAsync(tx.signAndSend()) + t.true(error instanceof SorobanRpc.AssembledTransaction.NeedsMoreSignaturesError, `error is not of type 'NeedsMoreSignaturesError'; instead it is of type '${error?.constructor.name}'`) + if (error) t.regex(error.message, /needsNonInvokerSigningBy/) +}) + +test('alice swaps bob 10 A for 1 B', async t => { + const swapContractAsRoot = swapContractAs(root) + const [ + { result: aliceStartingABalance }, + { result: aliceStartingBBalance }, + { result: bobStartingABalance }, + { result: bobStartingBBalance }, + ] = await Promise.all([ + tokenA.balance({ id: alicePk }), + tokenB.balance({ id: alicePk }), + tokenA.balance({ id: bobPk }), + tokenB.balance({ id: bobPk }), + ]) + t.true(aliceStartingABalance >= amountAToSwap, `alice does not have enough Token A! aliceStartingABalance: ${aliceStartingABalance}`) + t.true(bobStartingBBalance >= amountBToSwap, `bob does not have enough Token B! bobStartingBBalance: ${bobStartingBBalance}`) + + const tx = await swapContractAsRoot.swap({ + a: alicePk, + b: bobPk, + token_a: tokenAId, + token_b: tokenBId, + amount_a: amountAToSwap, + min_a_for_b: amountAToSwap, + amount_b: amountBToSwap, + min_b_for_a: amountBToSwap, + }) + + const needsNonInvokerSigningBy = await tx.needsNonInvokerSigningBy() + t.is(needsNonInvokerSigningBy.length, 2) + t.is(needsNonInvokerSigningBy.indexOf(alicePk), 0, 'needsNonInvokerSigningBy does not have alice\'s public key!') + t.is(needsNonInvokerSigningBy.indexOf(bobPk), 1, 'needsNonInvokerSigningBy does not have bob\'s public key!') + + + // root serializes & sends to alice + const jsonFromRoot = tx.toJSON() + const txAlice = swapContractAs(alice).txFromJSON(jsonFromRoot) + await txAlice.signAuthEntries() + + // alice serializes & sends to bob + const jsonFromAlice = txAlice.toJSON() + const txBob = swapContractAs(bob).txFromJSON(jsonFromAlice) + await txBob.signAuthEntries() + + // bob serializes & sends back to root + const jsonFromBob = txBob.toJSON() + const txRoot = swapContractAsRoot.txFromJSON(jsonFromBob) + const result = await txRoot.signAndSend() + + t.truthy(result.sendTransactionResponse, `tx failed: ${JSON.stringify(result, null, 2)}`) + t.is(result.sendTransactionResponse.status, 'PENDING', `tx failed: ${JSON.stringify(result, null, 2)}`) + t.truthy(result.getTransactionResponseAll?.length, `tx failed: ${JSON.stringify(result.getTransactionResponseAll, null, 2)}`) + t.not(result.getTransactionResponse.status, 'FAILED', `tx failed: ${JSON.stringify( + result.getTransactionResponse.resultXdr.result().value().map(op => + op.value()?.value().switch() + ), null, 2)}` + ) + t.is( + result.getTransactionResponse.status, + SorobanRpc.Api.GetTransactionStatus.SUCCESS, + `tx failed: ${JSON.stringify(result.getTransactionResponse, null, 2)}` + ) + + t.is( + (await tokenA.balance({ id: alicePk })).result, + aliceStartingABalance - amountAToSwap + ) + t.is( + (await tokenB.balance({ id: alicePk })).result, + aliceStartingBBalance + amountBToSwap + ) + t.is( + (await tokenA.balance({ id: bobPk })).result, + bobStartingABalance + amountAToSwap + ) + t.is( + (await tokenB.balance({ id: bobPk })).result, + bobStartingBBalance - amountBToSwap + ) +}) diff --git a/test/e2e/src/util.js b/test/e2e/src/util.js new file mode 100644 index 000000000..f8a2f0a7c --- /dev/null +++ b/test/e2e/src/util.js @@ -0,0 +1,64 @@ +const { spawnSync } = require('node:child_process') +const { Address, Keypair, TransactionBuilder, hash } = require('../../..') + +const rootKeypair = Keypair.fromSecret(spawnSync("./target/bin/soroban", ["config", "identity", "show"], { shell: true, encoding: "utf8" }).stdout.trim()); +const aliceKeypair = Keypair.fromSecret(spawnSync("./target/bin/soroban", ["config", "identity", "show", "alice"], { shell: true, encoding: "utf8" }).stdout.trim()); +const bobKeypair = Keypair.fromSecret(spawnSync("./target/bin/soroban", ["config", "identity", "show", "bob"], { shell: true, encoding: "utf8" }).stdout.trim()); + +const root = { + keypair: rootKeypair, + address: Address.fromString(rootKeypair.publicKey()), +} +module.exports.root = root + +const alice = { + keypair: aliceKeypair, + address: Address.fromString(aliceKeypair.publicKey()), +} +module.exports.alice = alice + +const bob = { + keypair: bobKeypair, + address: Address.fromString(bobKeypair.publicKey()), +} +module.exports.bob = bob + +function getKeypair(pk) { + return Keypair.fromSecret({ + [root.keypair.publicKey()]: root.keypair.secret(), + [alice.keypair.publicKey()]: alice.keypair.secret(), + [bob.keypair.publicKey()]: bob.keypair.secret(), + }[pk]) +} + +const rpcUrl = process.env.SOROBAN_RPC_URL ?? "http://localhost:8000/"; +module.exports.rpcUrl = rpcUrl +const networkPassphrase = process.env.SOROBAN_NETWORK_PASSPHRASE ?? "Standalone Network ; February 2017"; +module.exports.networkPassphrase = networkPassphrase + +class Wallet { + constructor(publicKey) { + this.publicKey = publicKey + } + + isConnected = () => Promise.resolve(true) // eslint-disable-line class-methods-use-this + + isAllowed = () => Promise.resolve(true) // eslint-disable-line class-methods-use-this + + getUserInfo = () => Promise.resolve({ publicKey: this.publicKey }) + + signTransaction = async (tx) => { + const t = TransactionBuilder.fromXDR(tx, networkPassphrase); + t.sign(getKeypair(this.publicKey)); + return t.toXDR(); + } + + signAuthEntry = async (entryXdr, opts) => ( + getKeypair(opts?.accountToSign ?? this.publicKey) + .sign(hash(Buffer.from(entryXdr, "base64"))) + .toString('base64') + ) +} +module.exports.Wallet = Wallet + +module.exports.wallet = new Wallet(root.keypair.publicKey()) diff --git a/test/e2e/wasms/README.md b/test/e2e/wasms/README.md new file mode 100644 index 000000000..d44181b20 --- /dev/null +++ b/test/e2e/wasms/README.md @@ -0,0 +1,16 @@ +Copy-pasted `.wasm` files (for now!) +==================================== + +These are built versions of contracts with source code that currently live in +the `test-wasms` directory within soroban-tools. Even there, they are mostly +copy-pasted contracts from `soroban-examples`. + +It would probably be best to include `soroban-examples` as a git submodule, +then directly rely on the contracts we want to test against. No more +copy-pasting! But until we can all agree on such an approach, maybe this is +good enough? + +Soon the CLI will have a `soroban init` command that allows setting up a new +Cargo workspace and may allow specifying which example contracts to put in it. +If the git submodule is controversial, then we could use that new `init` +command instead. diff --git a/test/e2e/wasms/specs/README.md b/test/e2e/wasms/specs/README.md new file mode 100644 index 000000000..accbb3c7a --- /dev/null +++ b/test/e2e/wasms/specs/README.md @@ -0,0 +1,10 @@ +Contract Specs, soon to be obsolete +=================================== + +Ok, so if you read the README in the parent directory, you know that we are currently including WASM files in this project because we don't yet have a good way of including the original Rust projects from which those WASM files are generated. + +We also don't yet have a way to fetch the XDR contract spec from deployed contracts, using stellar-sdk. Instead, we need to generate JSON files from the WASM files using the CLI (`soroban contract inspect`; see [initialize.sh](../../initialize.sh)). + +In the future, we can use something like `wasm-walrus-tools` to fetch the XDR contract spec from deployed contracts, and we won't need this directory anymore. + +These JSON files are generated automatically by the `initialize.sh` script, and are not included in the git repository. diff --git a/test/e2e/wasms/test_custom_types.wasm b/test/e2e/wasms/test_custom_types.wasm new file mode 100755 index 0000000000000000000000000000000000000000..9b34e216119fe68ddcee26283eba1af936b6e18a GIT binary patch literal 17388 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 literal 0 HcmV?d00001 diff --git a/test/e2e/wasms/test_token.wasm b/test/e2e/wasms/test_token.wasm new file mode 100755 index 0000000000000000000000000000000000000000..4de8827260e9e0372c47f1edc18574f7f84fb597 GIT binary patch literal 7471 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 literal 0 HcmV?d00001 diff --git a/test/unit/spec.json b/test/unit/spec.json index 887412199..0e178c0f9 100644 --- a/test/unit/spec.json +++ b/test/unit/spec.json @@ -1,34 +1,37 @@ -["AAAAAQAAAC9UaGlzIGlzIGZyb20gdGhlIHJ1c3QgZG9jIGFib3ZlIHRoZSBzdHJ1Y3QgVGVzdAAAAAAAAAAABFRlc3QAAAADAAAAAAAAAAFhAAAAAAAABAAAAAAAAAABYgAAAAAAAAEAAAAAAAAAAWMAAAAAAAAR", -"AAAAAgAAAAAAAAAAAAAAClNpbXBsZUVudW0AAAAAAAMAAAAAAAAAAAAAAAVGaXJzdAAAAAAAAAAAAAAAAAAABlNlY29uZAAAAAAAAAAAAAAAAAAFVGhpcmQAAAA=", -"AAAAAwAAAAAAAAAAAAAACVJveWFsQ2FyZAAAAAAAAAMAAAAAAAAABEphY2sAAAALAAAAAAAAAAVRdWVlbgAAAAAAAAwAAAAAAAAABEtpbmcAAAAN", -"AAAAAQAAAAAAAAAAAAAAC1R1cGxlU3RydWN0AAAAAAIAAAAAAAAAATAAAAAAAAfQAAAABFRlc3QAAAAAAAAAATEAAAAAAAfQAAAAClNpbXBsZUVudW0AAA==", -"AAAAAgAAAAAAAAAAAAAAC0NvbXBsZXhFbnVtAAAAAAUAAAABAAAAAAAAAAZTdHJ1Y3QAAAAAAAEAAAfQAAAABFRlc3QAAAABAAAAAAAAAAVUdXBsZQAAAAAAAAEAAAfQAAAAC1R1cGxlU3RydWN0AAAAAAEAAAAAAAAABEVudW0AAAABAAAH0AAAAApTaW1wbGVFbnVtAAAAAAABAAAAAAAAAAVBc3NldAAAAAAAAAIAAAATAAAACwAAAAAAAAAAAAAABFZvaWQ=", -"AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAAQAAABxQbGVhc2UgcHJvdmlkZSBhbiBvZGQgbnVtYmVyAAAAD051bWJlck11c3RCZU9kZAAAAAAB", -"AAAAAAAAAAAAAAAFaGVsbG8AAAAAAAABAAAAAAAAAAVoZWxsbwAAAAAAABEAAAABAAAAEQ==", -"AAAAAAAAAAAAAAAEd29pZAAAAAAAAAAA", -"AAAAAAAAAAAAAAADdmFsAAAAAAAAAAABAAAAAA==", -"AAAAAAAAAAAAAAAQdTMyX2ZhaWxfb25fZXZlbgAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAA+kAAAAEAAAAAw==", -"AAAAAAAAAAAAAAAEdTMyXwAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAAAQ=", -"AAAAAAAAAAAAAAAEaTMyXwAAAAEAAAAAAAAABGkzMl8AAAAFAAAAAQAAAAU=", -"AAAAAAAAAAAAAAAEaTY0XwAAAAEAAAAAAAAABGk2NF8AAAAHAAAAAQAAAAc=", -"AAAAAAAAACxFeGFtcGxlIGNvbnRyYWN0IG1ldGhvZCB3aGljaCB0YWtlcyBhIHN0cnVjdAAAAApzdHJ1a3RfaGVsAAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAPqAAAAEQ==", -"AAAAAAAAAAAAAAAGc3RydWt0AAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAfQAAAABFRlc3Q=", -"AAAAAAAAAAAAAAAGc2ltcGxlAAAAAAABAAAAAAAAAAZzaW1wbGUAAAAAB9AAAAAKU2ltcGxlRW51bQAAAAAAAQAAB9AAAAAKU2ltcGxlRW51bQAA", -"AAAAAAAAAAAAAAAHY29tcGxleAAAAAABAAAAAAAAAAdjb21wbGV4AAAAB9AAAAALQ29tcGxleEVudW0AAAAAAQAAB9AAAAALQ29tcGxleEVudW0A", -"AAAAAAAAAAAAAAAIYWRkcmVzc2UAAAABAAAAAAAAAAhhZGRyZXNzZQAAABMAAAABAAAAEw==", -"AAAAAAAAAAAAAAAFYnl0ZXMAAAAAAAABAAAAAAAAAAVieXRlcwAAAAAAAA4AAAABAAAADg==", -"AAAAAAAAAAAAAAAHYnl0ZXNfbgAAAAABAAAAAAAAAAdieXRlc19uAAAAA+4AAAAJAAAAAQAAA+4AAAAJ", -"AAAAAAAAAAAAAAAEY2FyZAAAAAEAAAAAAAAABGNhcmQAAAfQAAAACVJveWFsQ2FyZAAAAAAAAAEAAAfQAAAACVJveWFsQ2FyZAAAAA==", -"AAAAAAAAAAAAAAAHYm9vbGVhbgAAAAABAAAAAAAAAAdib29sZWFuAAAAAAEAAAABAAAAAQ==", -"AAAAAAAAABdOZWdhdGVzIGEgYm9vbGVhbiB2YWx1ZQAAAAADbm90AAAAAAEAAAAAAAAAB2Jvb2xlYW4AAAAAAQAAAAEAAAAB", -"AAAAAAAAAAAAAAAEaTEyOAAAAAEAAAAAAAAABGkxMjgAAAALAAAAAQAAAAs=", -"AAAAAAAAAAAAAAAEdTEyOAAAAAEAAAAAAAAABHUxMjgAAAAKAAAAAQAAAAo=", -"AAAAAAAAAAAAAAAKbXVsdGlfYXJncwAAAAAAAgAAAAAAAAABYQAAAAAAAAQAAAAAAAAAAWIAAAAAAAABAAAAAQAAAAQ=", -"AAAAAAAAAAAAAAADbWFwAAAAAAEAAAAAAAAAA21hcAAAAAPsAAAABAAAAAEAAAABAAAD7AAAAAQAAAAB", -"AAAAAAAAAAAAAAADdmVjAAAAAAEAAAAAAAAAA3ZlYwAAAAPqAAAABAAAAAEAAAPqAAAABA==", -"AAAAAAAAAAAAAAAFdHVwbGUAAAAAAAABAAAAAAAAAAV0dXBsZQAAAAAAA+0AAAACAAAAEQAAAAQAAAABAAAD7QAAAAIAAAARAAAABA==", -"AAAAAAAAAB9FeGFtcGxlIG9mIGFuIG9wdGlvbmFsIGFyZ3VtZW50AAAAAAZvcHRpb24AAAAAAAEAAAAAAAAABm9wdGlvbgAAAAAD6AAAAAQAAAABAAAD6AAAAAQ=", -"AAAAAAAAAAAAAAAEdTI1NgAAAAEAAAAAAAAABHUyNTYAAAAMAAAAAQAAAAw=", -"AAAAAAAAAAAAAAAEaTI1NgAAAAEAAAAAAAAABGkyNTYAAAANAAAAAQAAAA0=", -"AAAAAAAAAAAAAAAGc3RyaW5nAAAAAAABAAAAAAAAAAZzdHJpbmcAAAAAABAAAAABAAAAEA==", -"AAAAAAAAAAAAAAAMdHVwbGVfc3RydWt0AAAAAQAAAAAAAAAMdHVwbGVfc3RydWt0AAAH0AAAAAtUdXBsZVN0cnVjdAAAAAABAAAH0AAAAAtUdXBsZVN0cnVjdAA="] \ No newline at end of file +[ + "AAAAAQAAAC9UaGlzIGlzIGZyb20gdGhlIHJ1c3QgZG9jIGFib3ZlIHRoZSBzdHJ1Y3QgVGVzdAAAAAAAAAAABFRlc3QAAAADAAAAAAAAAAFhAAAAAAAABAAAAAAAAAABYgAAAAAAAAEAAAAAAAAAAWMAAAAAAAAR", + "AAAAAgAAAAAAAAAAAAAAClNpbXBsZUVudW0AAAAAAAMAAAAAAAAAAAAAAAVGaXJzdAAAAAAAAAAAAAAAAAAABlNlY29uZAAAAAAAAAAAAAAAAAAFVGhpcmQAAAA=", + "AAAAAwAAAAAAAAAAAAAACVJveWFsQ2FyZAAAAAAAAAMAAAAAAAAABEphY2sAAAALAAAAAAAAAAVRdWVlbgAAAAAAAAwAAAAAAAAABEtpbmcAAAAN", + "AAAAAQAAAAAAAAAAAAAAC1R1cGxlU3RydWN0AAAAAAIAAAAAAAAAATAAAAAAAAfQAAAABFRlc3QAAAAAAAAAATEAAAAAAAfQAAAAClNpbXBsZUVudW0AAA==", + "AAAAAgAAAAAAAAAAAAAAC0NvbXBsZXhFbnVtAAAAAAUAAAABAAAAAAAAAAZTdHJ1Y3QAAAAAAAEAAAfQAAAABFRlc3QAAAABAAAAAAAAAAVUdXBsZQAAAAAAAAEAAAfQAAAAC1R1cGxlU3RydWN0AAAAAAEAAAAAAAAABEVudW0AAAABAAAH0AAAAApTaW1wbGVFbnVtAAAAAAABAAAAAAAAAAVBc3NldAAAAAAAAAIAAAATAAAACwAAAAAAAAAAAAAABFZvaWQ=", + "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAAQAAABxQbGVhc2UgcHJvdmlkZSBhbiBvZGQgbnVtYmVyAAAAD051bWJlck11c3RCZU9kZAAAAAAB", + "AAAAAAAAAAAAAAAFaGVsbG8AAAAAAAABAAAAAAAAAAVoZWxsbwAAAAAAABEAAAABAAAAEQ==", + "AAAAAAAAAAAAAAAEd29pZAAAAAAAAAAA", + "AAAAAAAAAAAAAAADdmFsAAAAAAAAAAABAAAAAA==", + "AAAAAAAAAAAAAAAQdTMyX2ZhaWxfb25fZXZlbgAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAA+kAAAAEAAAAAw==", + "AAAAAAAAAAAAAAAEdTMyXwAAAAEAAAAAAAAABHUzMl8AAAAEAAAAAQAAAAQ=", + "AAAAAAAAAAAAAAAEaTMyXwAAAAEAAAAAAAAABGkzMl8AAAAFAAAAAQAAAAU=", + "AAAAAAAAAAAAAAAEaTY0XwAAAAEAAAAAAAAABGk2NF8AAAAHAAAAAQAAAAc=", + "AAAAAAAAACxFeGFtcGxlIGNvbnRyYWN0IG1ldGhvZCB3aGljaCB0YWtlcyBhIHN0cnVjdAAAAApzdHJ1a3RfaGVsAAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAPqAAAAEQ==", + "AAAAAAAAAAAAAAAGc3RydWt0AAAAAAABAAAAAAAAAAZzdHJ1a3QAAAAAB9AAAAAEVGVzdAAAAAEAAAfQAAAABFRlc3Q=", + "AAAAAAAAAAAAAAAGc2ltcGxlAAAAAAABAAAAAAAAAAZzaW1wbGUAAAAAB9AAAAAKU2ltcGxlRW51bQAAAAAAAQAAB9AAAAAKU2ltcGxlRW51bQAA", + "AAAAAAAAAAAAAAAHY29tcGxleAAAAAABAAAAAAAAAAdjb21wbGV4AAAAB9AAAAALQ29tcGxleEVudW0AAAAAAQAAB9AAAAALQ29tcGxleEVudW0A", + "AAAAAAAAAAAAAAAIYWRkcmVzc2UAAAABAAAAAAAAAAhhZGRyZXNzZQAAABMAAAABAAAAEw==", + "AAAAAAAAAAAAAAAFYnl0ZXMAAAAAAAABAAAAAAAAAAVieXRlcwAAAAAAAA4AAAABAAAADg==", + "AAAAAAAAAAAAAAAHYnl0ZXNfbgAAAAABAAAAAAAAAAdieXRlc19uAAAAA+4AAAAJAAAAAQAAA+4AAAAJ", + "AAAAAAAAAAAAAAAEY2FyZAAAAAEAAAAAAAAABGNhcmQAAAfQAAAACVJveWFsQ2FyZAAAAAAAAAEAAAfQAAAACVJveWFsQ2FyZAAAAA==", + "AAAAAAAAAAAAAAAHYm9vbGVhbgAAAAABAAAAAAAAAAdib29sZWFuAAAAAAEAAAABAAAAAQ==", + "AAAAAAAAABdOZWdhdGVzIGEgYm9vbGVhbiB2YWx1ZQAAAAADbm90AAAAAAEAAAAAAAAAB2Jvb2xlYW4AAAAAAQAAAAEAAAAB", + "AAAAAAAAAAAAAAAEaTEyOAAAAAEAAAAAAAAABGkxMjgAAAALAAAAAQAAAAs=", + "AAAAAAAAAAAAAAAEdTEyOAAAAAEAAAAAAAAABHUxMjgAAAAKAAAAAQAAAAo=", + "AAAAAAAAAAAAAAAKbXVsdGlfYXJncwAAAAAAAgAAAAAAAAABYQAAAAAAAAQAAAAAAAAAAWIAAAAAAAABAAAAAQAAAAQ=", + "AAAAAAAAAAAAAAADbWFwAAAAAAEAAAAAAAAAA21hcAAAAAPsAAAABAAAAAEAAAABAAAD7AAAAAQAAAAB", + "AAAAAAAAAAAAAAADdmVjAAAAAAEAAAAAAAAAA3ZlYwAAAAPqAAAABAAAAAEAAAPqAAAABA==", + "AAAAAAAAAAAAAAAFdHVwbGUAAAAAAAABAAAAAAAAAAV0dXBsZQAAAAAAA+0AAAACAAAAEQAAAAQAAAABAAAD7QAAAAIAAAARAAAABA==", + "AAAAAAAAAB9FeGFtcGxlIG9mIGFuIG9wdGlvbmFsIGFyZ3VtZW50AAAAAAZvcHRpb24AAAAAAAEAAAAAAAAABm9wdGlvbgAAAAAD6AAAAAQAAAABAAAD6AAAAAQ=", + "AAAAAAAAAAAAAAAEdTI1NgAAAAEAAAAAAAAABHUyNTYAAAAMAAAAAQAAAAw=", + "AAAAAAAAAAAAAAAEaTI1NgAAAAEAAAAAAAAABGkyNTYAAAANAAAAAQAAAA0=", + "AAAAAAAAAAAAAAAGc3RyaW5nAAAAAAABAAAAAAAAAAZzdHJpbmcAAAAAABAAAAABAAAAEA==", + "AAAAAAAAAAAAAAAMdHVwbGVfc3RydWt0AAAAAQAAAAAAAAAMdHVwbGVfc3RydWt0AAAH0AAAAAtUdXBsZVN0cnVjdAAAAAABAAAH0AAAAAtUdXBsZVN0cnVjdAA=" + ] + \ No newline at end of file diff --git a/test/unit/spec/contract_spec.ts b/test/unit/spec/contract_spec.ts index 25f611b07..99ea88fa3 100644 --- a/test/unit/spec/contract_spec.ts +++ b/test/unit/spec/contract_spec.ts @@ -1,4 +1,12 @@ -import { xdr, Address, ContractSpec, Keypair } from "../../../lib"; +import { + xdr, + Address, + ContractSpec, + Keypair, + SorobanRpc, + u32, + toLowerCamelCase, +} from "../../.."; import { JSONSchemaFaker } from "json-schema-faker"; import spec from "../spec.json"; @@ -7,6 +15,7 @@ import { expect } from "chai"; const publicKey = "GCBVOLOM32I7OD5TWZQCIXCXML3TK56MDY7ZMTAILIBQHHKPCVU42XYW"; const addr = Address.fromString(publicKey); let SPEC: ContractSpec; +let contract: any; JSONSchemaFaker.format("address", () => { let keypair = Keypair.random(); @@ -15,6 +24,7 @@ JSONSchemaFaker.format("address", () => { before(() => { SPEC = new ContractSpec(spec); + contract = SPEC.generateContractClient(OPTIONS); }); it("throws if no entries", () => { @@ -83,165 +93,189 @@ describe("Can round trip custom types", function () { }); }); - function roundtrip(funcName: string, input: any, typeName?: string) { + async function roundtrip(funcName: string, input: any, typeName?: string) { let type = getResultType(funcName); let ty = typeName ?? funcName; let obj: any = {}; obj[ty] = input; let scVal = SPEC.funcArgsToScVals(funcName, obj)[0]; - let result = SPEC.scValToNative(scVal, type); + let result = SPEC.funcResToNative(funcName, scVal); + let contract = SPEC.generateContractClient(OPTIONS); + //@ts-ignore + let { result: networkResult } = (await contract[toLowerCamelCase(funcName)]( + obj + )) as SorobanRpc.AssembledTransaction; + if (type.switch().value === xdr.ScSpecType.scSpecTypeResult().value) { + // @ts-ignore + result = result.unwrap(); + //@ts-ignore + networkResult = networkResult.unwrap(); + } expect(result).deep.equal(input); + expect(networkResult).deep.equal(input, "failed network call"); } - it("u32", () => { - roundtrip("u32_", 1); + it("u32", async () => { + await roundtrip("u32_", 1); }); - it("i32", () => { - roundtrip("i32_", -1); + it("i32", async () => { + await roundtrip("i32_", -1); }); - it("i64", () => { - roundtrip("i64_", 1n); + it("i64", async () => { + await roundtrip("i64_", 1n); }); - it("strukt", () => { - roundtrip("strukt", { a: 0, b: true, c: "hello" }); + it("strukt", async () => { + await roundtrip("strukt", { a: 0, b: true, c: "hello" }); }); describe("simple", () => { - it("first", () => { + it("first", async () => { const simple = { tag: "First" } as const; - roundtrip("simple", simple); + await roundtrip("simple", simple); }); - it("simple second", () => { + it("simple second", async () => { const simple = { tag: "Second" } as const; - roundtrip("simple", simple); + await roundtrip("simple", simple); }); - it("simple third", () => { + it("simple third", async () => { const simple = { tag: "Third" } as const; - roundtrip("simple", simple); + await roundtrip("simple", simple); }); }); describe("complex", () => { - it("struct", () => { + it("struct", async () => { const complex = { tag: "Struct", values: [{ a: 0, b: true, c: "hello" }], } as const; - roundtrip("complex", complex); + await roundtrip("complex", complex); }); - it("tuple", () => { + it("tuple", async () => { const complex = { tag: "Tuple", values: [[{ a: 0, b: true, c: "hello" }, { tag: "First" }]], } as const; - roundtrip("complex", complex); + await roundtrip("complex", complex); }); - it("enum", () => { + it("enum", async () => { const complex = { tag: "Enum", values: [{ tag: "First" }], } as const; - roundtrip("complex", complex); + await roundtrip("complex", complex); }); - it("asset", () => { + it("asset", async () => { const complex = { tag: "Asset", values: [addr.toString(), 1n] } as const; - roundtrip("complex", complex); + await roundtrip("complex", complex); }); - it("void", () => { + it("void", async () => { const complex = { tag: "Void" } as const; - roundtrip("complex", complex); + await roundtrip("complex", complex); }); }); - it("addresse", () => { - roundtrip("addresse", addr.toString()); + it("u32_fail_on_even", async () => { + await roundtrip("u32_fail_on_even", 1, "u32_"); + expect( + async () => + //@ts-ignore + ( + (await contract.u32FailOnEven({ + u32_: 2, + })) as SorobanRpc.AssembledTransaction + ).result + ); + }); + + it("addresse", async () => { + await roundtrip("addresse", addr.toString()); }); - it("bytes", () => { + it("bytes", async () => { const bytes = Buffer.from("hello"); - roundtrip("bytes", bytes); + await roundtrip("bytes", bytes); }); - it("bytes_n", () => { + it("bytes_n", async () => { const bytes_n = Buffer.from("123456789"); // what's the correct way to construct bytes_n? - roundtrip("bytes_n", bytes_n); + await roundtrip("bytes_n", bytes_n); }); - it("card", () => { + it("card", async () => { const card = 11; - roundtrip("card", card); + await roundtrip("card", card); }); - it("boolean", () => { - roundtrip("boolean", true); + it("boolean", async () => { + await roundtrip("boolean", true); }); - it("not", () => { - roundtrip("boolean", false); + it("not", async () => { + await roundtrip("boolean", false); }); - it("i128", () => { - roundtrip("i128", -1n); + it("i128", async () => { + await roundtrip("i128", -1n); }); - it("u128", () => { - roundtrip("u128", 1n); + it("u128", async () => { + await roundtrip("u128", 1n); }); - it("map", () => { + it("map", async () => { const map = new Map(); map.set(1, true); map.set(2, false); - roundtrip("map", [...map.entries()]); + await roundtrip("map", [...map.entries()]); map.set(3, "hahaha"); - expect(() => roundtrip("map", [...map.entries()])).to.throw( + await expectAsyncThrow( + async () => await roundtrip("map", [...map.entries()]), /invalid type scSpecTypeBool specified for string value/i ); }); - it("vec", () => { + it("vec", async () => { const vec = [1, 2, 3]; - roundtrip("vec", vec); + await roundtrip("vec", vec); }); - it("tuple", () => { + it("tuple", async () => { const tuple = ["hello", 1] as const; - roundtrip("tuple", tuple); + await roundtrip("tuple", tuple); }); - it("option", () => { - roundtrip("option", 1); - roundtrip("option", undefined); + it("option", async () => { + await roundtrip("option", 1); + await roundtrip("option", undefined); }); - it("u256", () => { - roundtrip("u256", 1n); - expect(() => roundtrip("u256", -1n)).to.throw( - /expected a positive value, got: -1/i - ); + it("u256", async () => { + await roundtrip("u256", 1n); + await expectAsyncThrow(async () => await roundtrip("u256", -1n), /expected a positive value, got: -1/i); }); - it("i256", () => { - roundtrip("i256", -1n); + it("i256", async () => { + await roundtrip("i256", -1n); }); - it("string", () => { - roundtrip("string", "hello"); + it("string", async () => { + await roundtrip("string", "hello"); }); - it("tuple_strukt", () => { + it("tuple_strukt", async () => { const arg = [{ a: 0, b: true, c: "hello" }, { tag: "First" }] as const; - roundtrip("tuple_strukt", arg); + await roundtrip("tuple_strukt", arg); }); }); @@ -371,3 +405,34 @@ function replaceBigIntWithStrings(obj: any): any { // Otherwise, return the value as it is return obj; } + +function getUserInfo() { + return { + publicKey: "GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4", + }; +} + +const OPTIONS = { + contractId: "CBUCOWICZPC3DYYD6ZAV25FEQNY47VJJB6K4XNYDNFT5J73LKTM7LEMB", + rpcUrl: "https://rpc-futurenet.stellar.org:443", + networkPassphrase: "Test SDF Future Network ; October 2022", + wallet: { + getUserInfo, + isConnected: () => true, + isAllowed: () => true, + } as unknown as SorobanRpc.Wallet, +}; + +// Utility function to test async error throwing +async function expectAsyncThrow(asyncFn: any, errorMessage: any) { + try { + await asyncFn(); + expect.fail("Did not throw expected error"); + } catch (error) { + if (errorMessage) { + expect(error.message).to.match(errorMessage); + } else { + expect(error).to.be.an("error"); + } + } +} diff --git a/test/tsconfig.json b/test/unit/tsconfig.json similarity index 82% rename from test/tsconfig.json rename to test/unit/tsconfig.json index c14ca34e4..2f55f76f0 100644 --- a/test/tsconfig.json +++ b/test/unit/tsconfig.json @@ -5,11 +5,11 @@ "removeComments": false, "lib": ["es2015"], "moduleResolution": "node", - "rootDir": "./unit/spec", - "outDir": "./unit/out", + "rootDir": "./spec", + "outDir": "./out", "target": "ES2020", "strict": false, }, "allowSyntheticDefaultImports": true, "include": ["**/*.ts" ] - } \ No newline at end of file + } diff --git a/yarn.lock b/yarn.lock index c2f04f760..5d7c65a8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1599,15 +1599,15 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.11.0", "@typescript-eslint/parser@^6.14.0": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.14.0.tgz#a2d6a732e0d2b95c73f6a26ae7362877cc1b4212" - integrity sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA== - dependencies: - "@typescript-eslint/scope-manager" "6.14.0" - "@typescript-eslint/types" "6.14.0" - "@typescript-eslint/typescript-estree" "6.14.0" - "@typescript-eslint/visitor-keys" "6.14.0" +"@typescript-eslint/parser@^6.11.0": + version "6.13.2" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.13.2.tgz" + integrity sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg== + dependencies: + "@typescript-eslint/scope-manager" "6.13.2" + "@typescript-eslint/types" "6.13.2" + "@typescript-eslint/typescript-estree" "6.13.2" + "@typescript-eslint/visitor-keys" "6.13.2" debug "^4.3.4" "@typescript-eslint/scope-manager@6.14.0": @@ -1843,7 +1843,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1: +acorn-walk@^8.1.1, acorn-walk@^8.2.0: version "8.3.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" integrity sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== @@ -1861,6 +1861,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +aggregate-error@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz" + integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w== + dependencies: + clean-stack "^4.0.0" + indent-string "^5.0.0" + ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -2004,6 +2012,11 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz" + integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== + array-includes@^3.1.7: version "3.1.7" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" @@ -2064,6 +2077,16 @@ arraybuffer.prototype.slice@^1.0.2: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" +arrgv@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz" + integrity sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw== + +arrify@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz" + integrity sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw== + asap@^2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -2112,6 +2135,55 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +ava@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz" + integrity sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg== + dependencies: + acorn "^8.8.2" + acorn-walk "^8.2.0" + ansi-styles "^6.2.1" + arrgv "^1.0.2" + arrify "^3.0.0" + callsites "^4.0.0" + cbor "^8.1.0" + chalk "^5.2.0" + chokidar "^3.5.3" + chunkd "^2.0.1" + ci-info "^3.8.0" + ci-parallel-vars "^1.0.1" + clean-yaml-object "^0.1.0" + cli-truncate "^3.1.0" + code-excerpt "^4.0.0" + common-path-prefix "^3.0.0" + concordance "^5.0.4" + currently-unhandled "^0.4.1" + debug "^4.3.4" + emittery "^1.0.1" + figures "^5.0.0" + globby "^13.1.4" + ignore-by-default "^2.1.0" + indent-string "^5.0.0" + is-error "^2.2.2" + is-plain-object "^5.0.0" + is-promise "^4.0.0" + matcher "^5.0.0" + mem "^9.0.2" + ms "^2.1.3" + p-event "^5.0.1" + p-map "^5.5.0" + picomatch "^2.3.1" + pkg-conf "^4.0.0" + plur "^5.1.0" + pretty-ms "^8.0.0" + resolve-cwd "^3.0.0" + stack-utils "^2.0.6" + strip-ansi "^7.0.1" + supertap "^3.0.1" + temp-dir "^3.0.0" + write-file-atomic "^5.0.1" + yargs "^17.7.2" + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -2239,6 +2311,11 @@ bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +blueimp-md5@^2.10.0: + version "2.19.0" + resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz" + integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -2446,6 +2523,11 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +callsites@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz" + integrity sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -2473,6 +2555,13 @@ catharsis@^0.9.0: dependencies: lodash "^4.17.15" +cbor@^8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz" + integrity sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg== + dependencies: + nofilter "^3.1.0" + chai-as-promised@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" @@ -2507,7 +2596,7 @@ chai@^4.3.10: pathval "^1.1.1" type-detect "^4.0.8" -chalk@5.3.0: +chalk@5.3.0, chalk@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -2548,7 +2637,7 @@ check-error@^1.0.2, check-error@^1.0.3: dependencies: get-func-name "^2.0.2" -chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.1: +chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.1, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -2573,11 +2662,21 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@^3.2.0: +chunkd@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz" + integrity sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ== + +ci-info@^3.2.0, ci-info@^3.8.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +ci-parallel-vars@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz" + integrity sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -2591,6 +2690,18 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +clean-stack@^4.0.0: + version "4.2.0" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz" + integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg== + dependencies: + escape-string-regexp "5.0.0" + +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz" + integrity sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw== + cli-cursor@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" @@ -2598,6 +2709,14 @@ cli-cursor@^4.0.0: dependencies: restore-cursor "^4.0.0" +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cli-truncate@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" @@ -2624,6 +2743,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2633,6 +2761,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +code-excerpt@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz" + integrity sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA== + dependencies: + convert-to-spaces "^2.0.1" + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -2741,6 +2876,20 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +concordance@^5.0.4: + version "5.0.4" + resolved "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz" + integrity sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw== + dependencies: + date-time "^3.1.0" + esutils "^2.0.3" + fast-diff "^1.2.0" + js-string-escape "^1.0.1" + lodash "^4.17.15" + md5-hex "^3.0.1" + semver "^7.3.2" + well-known-symbols "^2.0.0" + confusing-browser-globals@^1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" @@ -2786,6 +2935,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +convert-to-spaces@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz" + integrity sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ== + cookie@~0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -2890,6 +3044,13 @@ crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz" + integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== + dependencies: + array-find-index "^1.0.1" + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -2907,6 +3068,13 @@ date-format@^4.0.14: resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== +date-time@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz" + integrity sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg== + dependencies: + time-zone "^1.0.0" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3099,6 +3267,16 @@ domain-browser@^4.22.0: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.23.0.tgz#427ebb91efcb070f05cffdfb8a4e9a6c25f8c94b" integrity sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA== +dotenv@^16.3.1: + version "16.3.1" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -3130,6 +3308,11 @@ elliptic@^6.5.3, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emittery@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-1.0.1.tgz" + integrity sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ== + emoji-regex@^10.3.0: version "10.3.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" @@ -3140,6 +3323,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -3284,6 +3472,11 @@ escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -3517,7 +3710,7 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -esutils@^2.0.2: +esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -3620,7 +3813,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.2: +fast-diff@^1.1.2, fast-diff@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== @@ -3668,6 +3861,14 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +figures@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz" + integrity sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg== + dependencies: + escape-string-regexp "^5.0.0" + is-unicode-supported "^1.2.0" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3744,7 +3945,7 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^6.3.0: +find-up@^6.0.0, find-up@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790" integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== @@ -4053,6 +4254,17 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globby@^13.1.4: + version "13.2.2" + resolved "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -4261,6 +4473,11 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +ignore-by-default@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz" + integrity sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw== + ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: version "5.3.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" @@ -4292,6 +4509,11 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +indent-string@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz" + integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -4324,6 +4546,11 @@ ip-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== +irregular-plurals@^3.3.0: + version "3.5.0" + resolved "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz" + integrity sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ== + is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -4397,6 +4624,11 @@ is-docker@^3.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== +is-error@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz" + integrity sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4496,6 +4728,16 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -4552,6 +4794,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-unicode-supported@^1.2.0: + version "1.3.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -4713,6 +4960,11 @@ jest-worker@^29.5.0: merge-stream "^2.0.0" supports-color "^8.0.0" +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz" + integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4725,7 +4977,7 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.12.1, js-yaml@^3.13.1: +js-yaml@^3.12.1, js-yaml@^3.13.1, js-yaml@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -5017,6 +5269,11 @@ listr2@8.0.0: rfdc "^1.3.0" wrap-ansi "^9.0.0" +load-json-file@^7.0.0: + version "7.0.1" + resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz" + integrity sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ== + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -5176,6 +5433,13 @@ manage-path@2.0.0: resolved "https://registry.yarnpkg.com/manage-path/-/manage-path-2.0.0.tgz#f4cf8457b926eeee2a83b173501414bc76eb9597" integrity sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A== +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + markdown-it-anchor@^8.4.1: version "8.6.7" resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz#ee6926daf3ad1ed5e4e3968b1740eef1c6399634" @@ -5197,6 +5461,20 @@ marked@^4.0.10: resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== +matcher@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz" + integrity sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw== + dependencies: + escape-string-regexp "^5.0.0" + +md5-hex@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz" + integrity sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw== + dependencies: + blueimp-md5 "^2.10.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5216,6 +5494,14 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +mem@^9.0.2: + version "9.0.2" + resolved "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz" + integrity sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^4.0.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -5384,7 +5670,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5467,6 +5753,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +nofilter@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz" + integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== + "normalize-package-data@~1.0.1 || ^2.0.0 || ^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" @@ -5707,6 +5998,18 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + +p-event@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz" + integrity sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ== + dependencies: + p-timeout "^5.0.2" + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -5763,6 +6066,18 @@ p-map@^3.0.0: dependencies: aggregate-error "^3.0.0" +p-map@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz" + integrity sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg== + dependencies: + aggregate-error "^4.0.0" + +p-timeout@^5.0.2: + version "5.1.0" + resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz" + integrity sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew== + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -5801,6 +6116,11 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.6: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-ms@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz" + integrity sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -5904,6 +6224,14 @@ pirates@^4.0.5: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== +pkg-conf@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/pkg-conf/-/pkg-conf-4.0.0.tgz" + integrity sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w== + dependencies: + find-up "^6.0.0" + load-json-file "^7.0.0" + pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -5925,6 +6253,13 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +plur@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz" + integrity sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg== + dependencies: + irregular-plurals "^3.3.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -5942,6 +6277,13 @@ prettier@^3.1.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== +pretty-ms@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz" + integrity sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q== + dependencies: + parse-ms "^3.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -6355,7 +6697,7 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -"semver@2 >=2.2.1 || 3.x || 4 || 5 || 7", semver@^7.3.4, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +"semver@2 >=2.2.1 || 3.x || 4 || 5 || 7", semver@^7.3.2, semver@^7.3.4, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -6372,6 +6714,13 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -6461,7 +6810,7 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.1.0: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -6493,6 +6842,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" @@ -6637,6 +6991,13 @@ ssri@^8.0.0: dependencies: minipass "^3.1.1" +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -6696,7 +7057,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6705,6 +7066,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string-width@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.0.0.tgz#14aa1b7aaa126d5b64fa79d3c894da8a9650ba06" @@ -6769,7 +7139,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.1.0: +strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== @@ -6817,6 +7187,16 @@ superagent@^8.0.9: qs "^6.11.0" semver "^7.3.8" +supertap@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz" + integrity sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw== + dependencies: + indent-string "^5.0.0" + js-yaml "^3.14.1" + serialize-error "^7.0.1" + strip-ansi "^7.0.1" + supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" @@ -6882,6 +7262,11 @@ tar@^6.2.0: mkdirp "^1.0.3" yallist "^4.0.0" +temp-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz" + integrity sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw== + terser-webpack-plugin@^5.3.7, terser-webpack-plugin@^5.3.9: version "5.3.9" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" @@ -6917,6 +7302,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +time-zone@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz" + integrity sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA== + timers-browserify@^2.0.12: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -7039,6 +7429,11 @@ type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -7154,6 +7549,7 @@ typedarray@^0.0.6: integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== "typescript-5.3@npm:typescript@~5.3.0-0", typescript@^5.3.3: + name typescript-5.3 version "5.3.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== @@ -7417,6 +7813,11 @@ webpack@^5.88.2: watchpack "^2.4.0" webpack-sources "^3.2.3" +well-known-symbols@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz" + integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -7524,6 +7925,14 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + ws@~8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" @@ -7582,6 +7991,11 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs-unparser@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" @@ -7622,6 +8036,19 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From af361a58c605d0a38fc2e86b69fde83f7b4ef9fb Mon Sep 17 00:00:00 2001 From: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:28:29 -0500 Subject: [PATCH 2/2] chore: rollback test/contract_spec changes ContractClient is now fully tested in `tests/e2e` rather than in this file Keeping this in a separate commit in case we decide it's better to go back and do things "The tests/unit way" instead of the new tests/e2e way. It feels maybe silly to have both. --- test/unit/spec/contract_spec.ts | 198 +++++++++++--------------------- 1 file changed, 69 insertions(+), 129 deletions(-) diff --git a/test/unit/spec/contract_spec.ts b/test/unit/spec/contract_spec.ts index 99ea88fa3..861a7c968 100644 --- a/test/unit/spec/contract_spec.ts +++ b/test/unit/spec/contract_spec.ts @@ -1,12 +1,4 @@ -import { - xdr, - Address, - ContractSpec, - Keypair, - SorobanRpc, - u32, - toLowerCamelCase, -} from "../../.."; +import { xdr, Address, ContractSpec, Keypair } from "../../../lib"; import { JSONSchemaFaker } from "json-schema-faker"; import spec from "../spec.json"; @@ -15,7 +7,6 @@ import { expect } from "chai"; const publicKey = "GCBVOLOM32I7OD5TWZQCIXCXML3TK56MDY7ZMTAILIBQHHKPCVU42XYW"; const addr = Address.fromString(publicKey); let SPEC: ContractSpec; -let contract: any; JSONSchemaFaker.format("address", () => { let keypair = Keypair.random(); @@ -24,7 +15,6 @@ JSONSchemaFaker.format("address", () => { before(() => { SPEC = new ContractSpec(spec); - contract = SPEC.generateContractClient(OPTIONS); }); it("throws if no entries", () => { @@ -93,189 +83,170 @@ describe("Can round trip custom types", function () { }); }); - async function roundtrip(funcName: string, input: any, typeName?: string) { + function roundtrip(funcName: string, input: any, typeName?: string) { let type = getResultType(funcName); let ty = typeName ?? funcName; let obj: any = {}; obj[ty] = input; let scVal = SPEC.funcArgsToScVals(funcName, obj)[0]; - let result = SPEC.funcResToNative(funcName, scVal); - let contract = SPEC.generateContractClient(OPTIONS); - //@ts-ignore - let { result: networkResult } = (await contract[toLowerCamelCase(funcName)]( - obj - )) as SorobanRpc.AssembledTransaction; - if (type.switch().value === xdr.ScSpecType.scSpecTypeResult().value) { - // @ts-ignore - result = result.unwrap(); - //@ts-ignore - networkResult = networkResult.unwrap(); - } + let result = SPEC.scValToNative(scVal, type); expect(result).deep.equal(input); - expect(networkResult).deep.equal(input, "failed network call"); } - it("u32", async () => { - await roundtrip("u32_", 1); + it("u32", () => { + roundtrip("u32_", 1); + }); + + it("u32_fail_on_even", async () => { + roundtrip("u32_fail_on_even", 1, "u32_"); + roundtrip("u32_fail_on_even", 2, "u32_"); }); - it("i32", async () => { - await roundtrip("i32_", -1); + it("i32", () => { + roundtrip("i32_", -1); }); - it("i64", async () => { - await roundtrip("i64_", 1n); + it("i64", () => { + roundtrip("i64_", 1n); }); - it("strukt", async () => { - await roundtrip("strukt", { a: 0, b: true, c: "hello" }); + it("strukt", () => { + roundtrip("strukt", { a: 0, b: true, c: "hello" }); }); describe("simple", () => { - it("first", async () => { + it("first", () => { const simple = { tag: "First" } as const; - await roundtrip("simple", simple); + roundtrip("simple", simple); }); - it("simple second", async () => { + it("simple second", () => { const simple = { tag: "Second" } as const; - await roundtrip("simple", simple); + roundtrip("simple", simple); }); - it("simple third", async () => { + it("simple third", () => { const simple = { tag: "Third" } as const; - await roundtrip("simple", simple); + roundtrip("simple", simple); }); }); describe("complex", () => { - it("struct", async () => { + it("struct", () => { const complex = { tag: "Struct", values: [{ a: 0, b: true, c: "hello" }], } as const; - await roundtrip("complex", complex); + roundtrip("complex", complex); }); - it("tuple", async () => { + it("tuple", () => { const complex = { tag: "Tuple", values: [[{ a: 0, b: true, c: "hello" }, { tag: "First" }]], } as const; - await roundtrip("complex", complex); + roundtrip("complex", complex); }); - it("enum", async () => { + it("enum", () => { const complex = { tag: "Enum", values: [{ tag: "First" }], } as const; - await roundtrip("complex", complex); + roundtrip("complex", complex); }); - it("asset", async () => { + it("asset", () => { const complex = { tag: "Asset", values: [addr.toString(), 1n] } as const; - await roundtrip("complex", complex); + roundtrip("complex", complex); }); - it("void", async () => { + it("void", () => { const complex = { tag: "Void" } as const; - await roundtrip("complex", complex); + roundtrip("complex", complex); }); }); - it("u32_fail_on_even", async () => { - await roundtrip("u32_fail_on_even", 1, "u32_"); - expect( - async () => - //@ts-ignore - ( - (await contract.u32FailOnEven({ - u32_: 2, - })) as SorobanRpc.AssembledTransaction - ).result - ); - }); - - it("addresse", async () => { - await roundtrip("addresse", addr.toString()); + it("addresse", () => { + roundtrip("addresse", addr.toString()); }); - it("bytes", async () => { + it("bytes", () => { const bytes = Buffer.from("hello"); - await roundtrip("bytes", bytes); + roundtrip("bytes", bytes); }); - it("bytes_n", async () => { + it("bytes_n", () => { const bytes_n = Buffer.from("123456789"); // what's the correct way to construct bytes_n? - await roundtrip("bytes_n", bytes_n); + roundtrip("bytes_n", bytes_n); }); - it("card", async () => { + it("card", () => { const card = 11; - await roundtrip("card", card); + roundtrip("card", card); }); - it("boolean", async () => { - await roundtrip("boolean", true); + it("boolean", () => { + roundtrip("boolean", true); }); - it("not", async () => { - await roundtrip("boolean", false); + it("not", () => { + roundtrip("boolean", false); }); - it("i128", async () => { - await roundtrip("i128", -1n); + it("i128", () => { + roundtrip("i128", -1n); }); - it("u128", async () => { - await roundtrip("u128", 1n); + it("u128", () => { + roundtrip("u128", 1n); }); - it("map", async () => { + it("map", () => { const map = new Map(); map.set(1, true); map.set(2, false); - await roundtrip("map", [...map.entries()]); + roundtrip("map", [...map.entries()]); map.set(3, "hahaha"); - await expectAsyncThrow( - async () => await roundtrip("map", [...map.entries()]), + expect(() => roundtrip("map", [...map.entries()])).to.throw( /invalid type scSpecTypeBool specified for string value/i ); }); - it("vec", async () => { + it("vec", () => { const vec = [1, 2, 3]; - await roundtrip("vec", vec); + roundtrip("vec", vec); }); - it("tuple", async () => { + it("tuple", () => { const tuple = ["hello", 1] as const; - await roundtrip("tuple", tuple); + roundtrip("tuple", tuple); }); - it("option", async () => { - await roundtrip("option", 1); - await roundtrip("option", undefined); + it("option", () => { + roundtrip("option", 1); + roundtrip("option", undefined); }); - it("u256", async () => { - await roundtrip("u256", 1n); - await expectAsyncThrow(async () => await roundtrip("u256", -1n), /expected a positive value, got: -1/i); + it("u256", () => { + roundtrip("u256", 1n); + expect(() => roundtrip("u256", -1n)).to.throw( + /expected a positive value, got: -1/i + ); }); - it("i256", async () => { - await roundtrip("i256", -1n); + it("i256", () => { + roundtrip("i256", -1n); }); - it("string", async () => { - await roundtrip("string", "hello"); + it("string", () => { + roundtrip("string", "hello"); }); - it("tuple_strukt", async () => { + it("tuple_strukt", () => { const arg = [{ a: 0, b: true, c: "hello" }, { tag: "First" }] as const; - await roundtrip("tuple_strukt", arg); + roundtrip("tuple_strukt", arg); }); }); @@ -405,34 +376,3 @@ function replaceBigIntWithStrings(obj: any): any { // Otherwise, return the value as it is return obj; } - -function getUserInfo() { - return { - publicKey: "GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4", - }; -} - -const OPTIONS = { - contractId: "CBUCOWICZPC3DYYD6ZAV25FEQNY47VJJB6K4XNYDNFT5J73LKTM7LEMB", - rpcUrl: "https://rpc-futurenet.stellar.org:443", - networkPassphrase: "Test SDF Future Network ; October 2022", - wallet: { - getUserInfo, - isConnected: () => true, - isAllowed: () => true, - } as unknown as SorobanRpc.Wallet, -}; - -// Utility function to test async error throwing -async function expectAsyncThrow(asyncFn: any, errorMessage: any) { - try { - await asyncFn(); - expect.fail("Did not throw expected error"); - } catch (error) { - if (errorMessage) { - expect(error.message).to.match(errorMessage); - } else { - expect(error).to.be.an("error"); - } - } -}