diff --git a/.gitmodules b/.gitmodules index 76a36100bc..9e993ea759 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "src/snarkyjs-bindings"] +[submodule "src/o1js-bindings"] path = src/bindings - url = https://github.com/o1-labs/snarkyjs-bindings.git + url = https://github.com/o1-labs/o1js-bindings.git [submodule "src/mina"] path = src/mina url = https://github.com/MinaProtocol/mina.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e8c745f3e..998774da6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/o1js/compare/08ba27329...HEAD) +## [Unreleased](https://github.com/o1-labs/o1js/compare/be748e42e...HEAD) + +## [0.15.3](https://github.com/o1-labs/o1js/compare/1ad7333e9e...be748e42e) + +### Breaking changes + +- `Mina.accountCreationFee()` is deprecated in favor of `Mina.getNetworkConstants().accountCreationFee`. https://github.com/o1-labs/o1js/pull/1367 + - `Mina.getNetworkConstants()` returns: + - [default](https://github.com/o1-labs/o1js/pull/1367/files#diff-ef2c3547d64a8eaa8253cd82b3623288f3271e14f1dc893a0a3ddc1ff4b9688fR7) network constants if used outside of the transaction scope. + - [actual](https://github.com/o1-labs/o1js/pull/1367/files#diff-437f2c15df7c90ad8154c5de1677ec0838d51859bcc0a0cefd8a0424b5736f31R1051) network constants if used within the transaction scope. ### Added diff --git a/README-dev.md b/README-dev.md index c81b561d08..c273dd8186 100644 --- a/README-dev.md +++ b/README-dev.md @@ -31,52 +31,52 @@ npm install npm run build ``` -This will compile the TypeScript source files, making it ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end-users. These artifacts are stored under `src/bindings/compiled`, and contain the artifacts needed for both node and web builds. These files do not have to be regenerated unless there are changes to the OCaml or Rust source files. +This command compiles the TypeScript source files, making them ready for use. The compiled OCaml and WebAssembly artifacts are version-controlled to simplify the build process for end users. These artifacts are stored under `src/bindings/compiled` and contain the artifacts needed for both node and web builds. These files only have to be regenerated if there are changes to the OCaml or Rust source files. ## Building Bindings -If you need to regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js, so you can build them from within the o1js repo. +To regenerate the OCaml and WebAssembly artifacts, you can do so within the o1js repo. The [bindings](https://github.com/o1-labs/o1js-bindings) and [Mina](https://github.com/MinaProtocol/mina) repos are both submodules of o1js so you can build them from within the o1js repo. -o1js depends on OCaml code that is transpiled to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml), and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. +o1js depends on OCaml code that is transpiled to JavaScript using [Js_of_ocaml](https://github.com/ocsigen/js_of_ocaml) and Rust code that is transpiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack). These artifacts allow o1js to call into [Pickles](https://github.com/MinaProtocol/mina/blob/develop/src/lib/pickles/README.md), [snarky](https://github.com/o1-labs/snarky), and [Kimchi](https://github.com/o1-labs/proof-systems) to write zk-SNARKs and zkApps. -The compiled artifacts are stored under `src/bindings/compiled`, and are version-controlled to simplify the build process for end-users. +The compiled artifacts are stored under `src/bindings/compiled` and are version-controlled to simplify the build process for end-users. -If you wish to rebuild the OCaml and Rust artifacts, you must be able to build the Mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. Once you have configured your environment to build Mina, you can build the bindings: +If you want to rebuild the OCaml and Rust artifacts, you must be able to build the mina repo before building the bindings. See the [Mina Dev Readme](https://github.com/MinaProtocol/mina/blob/develop/README-dev.md) for more information. After you have configured your environment to build mina, you can build the bindings: ```sh npm run build:update-bindings ``` -This will build the OCaml and Rust artifacts, and copy them to the `src/bindings/compiled` directory. +This command builds the OCaml and Rust artifacts and copies them to the `src/bindings/compiled` directory. ### Build Scripts -The root build script which kicks off the build process is under `src/bindings/scripts/update-snarkyjs-bindings.sh`. This script is responsible for building the Node.js and web artifacts for o1js, and places them under `src/bindings/compiled`, to be used by o1js. +The root build script which kicks off the build process is under `src/bindings/scripts/update-o1js-bindings.sh`. This script is responsible for building the Node.js and web artifacts for o1js, and places them under `src/bindings/compiled`, to be used by o1js. ### OCaml Bindings o1js depends on Pickles, snarky, and parts of the Mina transaction logic, all of which are compiled to JavaScript and stored as artifacts to be used by o1js natively. The OCaml bindings are located under `src/bindings`. See the [OCaml Bindings README](https://github.com/o1-labs/o1js-bindings/blob/main/README.md) for more information. -To compile the OCaml code, a build tool called Dune is used. Dune is a build system for OCaml projects, and is used in addition with Js_of_ocaml to compile the OCaml code to JavaScript. The dune file that is responsible for compiling the OCaml code is located under `src/bindings/ocaml/dune`. There are two build targets: `snarky_js_node` and `snarky_js_web`, which compile the Mina dependencies as well as link the wasm artifacts to build the Node.js and web artifacts, respectively. The output file is `snark_js_node.bc.js`, which is used by o1js. +To compile the OCaml code, a build tool called Dune is used. Dune is a build system for OCaml projects, and is used in addition with Js_of_ocaml to compile the OCaml code to JavaScript. The dune file that is responsible for compiling the OCaml code is located under `src/bindings/ocaml/dune`. There are two build targets: `o1js_node` and `o1js_web`, which compile the Mina dependencies as well as link the wasm artifacts to build the Node.js and web artifacts, respectively. The output file is `o1js_node.bc.js`, which is used by o1js. ### WebAssembly Bindings -o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo, under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. +o1js additionally depends on Kimchi, which is compiled to WebAssembly. Kimchi is located in the Mina repo under `src/mina`. See the [Kimchi README](https://github.com/o1-labs/proof-systems/blob/master/README.md) for more information. -To compile the wasm code, a combination of Cargo and Dune is used. Both build files are located under `src/mina/src/lib/crypto/kimchi`, where the `wasm` folder contains the Rust code which is compiled to wasm, and the `js` folder which contains a wrapper around the wasm code which allows Js_of_ocaml to compile against the wasm backend. +To compile the Wasm code, a combination of Cargo and Dune is used. Both build files are located under `src/mina/src/lib/crypto/kimchi`, where the `wasm` folder contains the Rust code that is compiled to Wasm, and the `js` folder that contains a wrapper around the Wasm code which allows Js_of_ocaml to compile against the Wasm backend. -For the wasm build, the output files are: +For the Wasm build, the output files are: - `plonk_wasm_bg.wasm`: The compiled WebAssembly binary. - `plonk_wasm_bg.wasm.d.ts`: TypeScript definition files describing the types of .wasm or .js files. -- `plonk_wasm.js`: JavaScript file that wraps the WASM code for use in Node.js. +- `plonk_wasm.js`: JavaScript file that wraps the Wasm code for use in Node.js. - `plonk_wasm.d.ts`: TypeScript definition file for plonk_wasm.js. ### Generated Constant Types -In addition to building the OCaml and Rust code, the build script also generates TypeScript types for constants used in the Mina protocol. These types are generated from the OCaml source files, and are located under `src/bindings/crypto/constants.ts` and `src/bindings/mina-transaction/gen`. When building the bindings, these constants are auto-generated by Dune. If you wish to add a new constant, you can edit the `src/bindings/ocaml/snarky_js_constants` file, and then run `npm run build:bindings` to regenerate the TypeScript files. +In addition to building the OCaml and Rust code, the build script also generates TypeScript types for constants used in the Mina protocol. These types are generated from the OCaml source files, and are located under `src/bindings/crypto/constants.ts` and `src/bindings/mina-transaction/gen`. When building the bindings, these constants are auto-generated by Dune. If you wish to add a new constant, you can edit the `src/bindings/ocaml/o1js_constants` file, and then run `npm run build:bindings` to regenerate the TypeScript files. -These types are used by o1js to ensure that the constants used in the protocol are consistent with the OCaml source files. +o1js uses these types to ensure that the constants used in the protocol are consistent with the OCaml source files. ## Development @@ -88,7 +88,7 @@ If you work on o1js, create a feature branch off of one of these base branches. **Default to `main` as the base branch**. -The other base branches (`berkeley`, `develop`) are only used in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. +The other base branches (`berkeley` and `develop`) are used only in specific scenarios where you want to adapt o1js to changes in the sibling repos on those other branches. Even then, consider whether it is feasible to land your changes to `main` and merge to `berkeley` and `develop` afterwards. Only changes in `main` will ever be released, so anything in the other branches has to be backported and reconciled with `main` eventually. | Repository | mina -> o1js -> o1js-bindings | | ---------- | -------------------------------- | @@ -96,13 +96,13 @@ The other base branches (`berkeley`, `develop`) are only used in specific scenar | | berkeley -> berkeley -> berkeley | | | develop -> develop -> develop | -- `o1js-main`: The o1js-main branch in the Mina repository corresponds to the main branch in both o1js and o1js-bindings repositories. This is where stable releases and ramp-up features are maintained. The o1js-main branch runs in parallel to the Mina `berkeley` branch and does not have a subset or superset relationship with it. The branching structure is as follows (<- means direction to merge): +- `o1js-main`: The `o1js-main` branch in the Mina repository corresponds to the `main` branch in both o1js and o1js-bindings repositories. This branch is where stable releases and ramp-up features are maintained. The `o1js-main` branch runs in parallel to the Mina `berkeley` branch and does not have a subset or superset relationship with it. The branching structure is as follows (<- means direction to merge): - - `develop` <- `o1js-main` <- `current testnet` - Typically, the current testnet often corresponds to the rampup branch. + - `develop` <- `o1js-main` <- `current testnet` - Typically, the current Testnet often corresponds to the rampup branch. -- `berkeley`: The berkeley branch is maintained across all three repositories. This branch is used for features and updates specific to the Berkeley release of the project. +- `berkeley`: The `berkeley` branch is maintained across all three repositories. This branch is used for features and updates specific to the Berkeley release of the project. -- `develop`: The develop branch is also maintained across all three repositories. It is used for ongoing development, testing new features, and integration work. +- `develop`: The `develop` branch is also maintained across all three repositories. It is used for ongoing development, testing new features, and integration work. ### Running Tests @@ -113,15 +113,15 @@ npm run test npm run test:unit ``` -This will run all the unit tests and provide you with a summary of the test results. +This runs all the unit tests and provides you with a summary of the test results. -You can additionally run integration tests by running: +You can also run integration tests by running: ```sh npm run test:integration ``` -Finally, we have a set of end-to-end tests that run against the browser. These tests are not run by default, but you can run them by running: +Finally, a set of end-to-end tests are run against the browser. These tests are not run by default, but you can run them by running: ```sh npm install @@ -137,7 +137,7 @@ npm run e2e:show-report -You can execute the CI locally by using [act](https://github.com/nektos/act). First generate a GitHub token and use: +You can execute the CI locally by using [act](https://github.com/nektos/act). First, generate a GitHub token and use: ```sh act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $GITHUB_TOKEN @@ -145,14 +145,15 @@ act -j Build-And-Test-Server --matrix test_type:"Simple integration tests" -s $G ### Releasing -To release a new version of o1js, you must first update the version number in `package.json`. Then, you can create a new pull request to merge your changes into the main branch. Once the pull request is merged, a CI job will automatically publish the new version to npm. +To release a new version of o1js, you must first update the version number in `package.json`. Then, you can create a new pull request to merge your changes into the main branch. After the pull request is merged, a CI job automatically publishes the new version to npm. -## Test zkApps against the local blockchain network +## Testing and Debugging -In order to be able to test zkApps against the local blockchain network, you need to spin up such a network first. -You can do so in several ways. +### Test zkApps against Lightnet network -1. Using [zkapp-cli](https://www.npmjs.com/package/zkapp-cli)'s sub commands: +Use the lightweight Mina blockchain network (Lightnet) to test on a local blockchain before you test with a live network. To test zkApps against the local blockchain, first spin up Lightnet. + +The easiest way is to use [zkApp CLI](https://www.npmjs.com/package/zkapp-cli) sub-commands: ```shell zk lightnet start # start the local network @@ -162,9 +163,9 @@ You can do so in several ways. zk lightnet stop # stop the local network ``` - Please refer to `zk lightnet --help` for more information. + Use `zk lightnet --help` for more information. -2. Using the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: +You can also use the corresponding [Docker image](https://hub.docker.com/r/o1labs/mina-local-network) manually: ```shell docker run --rm --pull=missing -it \ @@ -179,8 +180,24 @@ You can do so in several ways. o1labs/mina-local-network:o1js-main-latest-lightnet ``` - Please refer to the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. + See the [Docker Hub repository](https://hub.docker.com/r/o1labs/mina-local-network) for more information. + +Next up, get the Mina blockchain accounts information to be used in your zkApp. +After the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. +See the corresponding example in [src/examples/zkapps/hello_world/run_live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello_world/run_live.ts). + +### Profiling o1js + +To enhance the development experience and optimize the performance of o1js, use the Chrome Debugger alongside Node.js. This setup is particularly useful when you want to profile the performance of your zkApp or o1js. + +#### Using the `run-debug` script + +To facilitate this process, use the provided script named `run-debug`. To use this script, run: + +```sh +./run-debug --bundle +``` + +This script initializes a Node.js process with the `--inspect-brk` flag that starts the Node.js inspector and breaks before the user script starts (i.e., it pauses execution until a debugger is attached). The `--enable-source-maps` flag ensures that source maps are used to allow easy debugging of o1js code directly. -Next up, you will need the Mina blockchain accounts information in order to be used in your zkApp. -Once the local network is up and running, you can use the [Lightnet](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/lib/fetch.ts#L1012) `o1js API namespace` to get the accounts information. -The corresponding example can be found here: [src/examples/zkapps/hello_world/run_live.ts](https://github.com/o1-labs/o1js/blob/ec789794b2067addef6b6f9c9a91c6511e07e37c/src/examples/zkapps/hello_world/run_live.ts) +After the Node.js process is running, open the Chrome browser and navigate to `chrome://inspect` to attach the Chrome Debugger to the Node.js process. You can set breakpoints, inspect variables, and profile the performance of your zkApp or o1js. For more information on using the Chrome Debugger, see the [DevTools documentation](https://developer.chrome.com/docs/devtools/). diff --git a/package-lock.json b/package-lock.json index e0edcd8ffa..d7e23d73cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "o1js", - "version": "0.15.2", + "version": "0.15.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "o1js", - "version": "0.15.2", + "version": "0.15.3", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 9402f07279..affed9efdc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "o1js", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.15.2", + "version": "0.15.3", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/o1js/", "keywords": [ @@ -44,8 +44,8 @@ "scripts": { "dev": "npx tsc -p tsconfig.test.json && node src/build/copy-to-dist.js", "build": "node src/build/copy-artifacts.js && rimraf ./dist/node && npm run dev && node src/build/buildNode.js", - "build:bindings": "./src/bindings/scripts/build-snarkyjs-node.sh", - "build:update-bindings": "./src/bindings/scripts/update-snarkyjs-bindings.sh", + "build:bindings": "./src/bindings/scripts/build-o1js-node.sh", + "build:update-bindings": "./src/bindings/scripts/update-o1js-bindings.sh", "build:wasm": "./src/bindings/scripts/update-wasm-and-types.sh", "build:web": "rimraf ./dist/web && node src/build/buildWeb.js", "build:examples": "npm run build && rimraf ./dist/examples && npx tsc -p tsconfig.examples.json", diff --git a/run-ci-live-tests.sh b/run-ci-live-tests.sh index ba881bb5c7..192041b540 100755 --- a/run-ci-live-tests.sh +++ b/run-ci-live-tests.sh @@ -17,6 +17,8 @@ echo "" HELLO_WORLD_PROC=$! ./run src/examples/zkapps/dex/run_live.ts --bundle | add_prefix "DEX" & DEX_PROC=$! +./run src/examples/fetch_live.ts --bundle | add_prefix "FETCH" & +FETCH_PROC=$! # Wait for each process and capture their exit statuses FAILURE=0 @@ -34,6 +36,13 @@ if [ $? -ne 0 ]; then echo "" FAILURE=1 fi +wait $FETCH_PROC +if [ $? -ne 0 ]; then + echo "" + echo "FETCH test failed." + echo "" + FAILURE=1 +fi # Exit with failure if any process failed if [ $FAILURE -ne 0 ]; then diff --git a/src/build/buildWeb.js b/src/build/buildWeb.js index b2a4929859..b0455e62b5 100644 --- a/src/build/buildWeb.js +++ b/src/build/buildWeb.js @@ -59,7 +59,7 @@ async function buildWeb({ production }) { await Promise.all([tscPromise, copyPromise]); if (minify) { - let o1jsWebPath = './dist/web/web_bindings/snarky_js_web.bc.js'; + let o1jsWebPath = './dist/web/web_bindings/o1js_web.bc.js'; let o1jsWeb = await readFile(o1jsWebPath, 'utf8'); let { code } = await esbuild.transform(o1jsWeb, { target, diff --git a/src/examples/fetch_live.ts b/src/examples/fetch_live.ts new file mode 100644 index 0000000000..85ce4b8d4d --- /dev/null +++ b/src/examples/fetch_live.ts @@ -0,0 +1,92 @@ +import { expect } from 'expect'; +import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; + +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +Mina.setActiveInstance(configureMinaNetwork()); +const transactionFee = 100_000_000; +let defaultNetworkConstants: Mina.NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(1_000_000_000), +}; +const { sender } = await configureFeePayer(); + +await checkDefaultNetworkConstantsFetching(); +await checkActualNetworkConstantsFetching(); + +await tearDown(); + +async function checkDefaultNetworkConstantsFetching() { + console.log( + '\nCase #1: check that default network constants can be fetched outside of the transaction scope.' + ); + const networkConstants = Mina.getNetworkConstants(); + expect(defaultNetworkConstants).toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +async function checkActualNetworkConstantsFetching() { + console.log( + '\nCase #2: check that actual network constants can be fetched within the transaction scope.' + ); + let networkConstants: Mina.NetworkConstants | undefined; + await Mina.transaction({ sender, fee: transactionFee }, () => { + networkConstants = Mina.getNetworkConstants(); + }); + expect(networkConstants?.slotTime).not.toBeUndefined(); + expect(networkConstants?.genesisTimestamp).not.toBeUndefined(); + expect(defaultNetworkConstants).not.toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +function configureMinaNetwork() { + const minaGraphQlEndpoint = useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://berkeley.minascan.io/graphql'; + return Mina.Network({ + mina: minaGraphQlEndpoint, + archive: useCustomLocalNetwork + ? 'http://localhost:8282' + : 'https://api.minascan.io/archive/berkeley/v1/graphql', + lightnetAccountManager: 'http://localhost:8181', + }); +} + +async function configureFeePayer() { + const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); + const sender = senderKey.toPublicKey(); + if (!useCustomLocalNetwork) { + console.log(`\nFunding the fee payer account.`); + await Mina.faucet(sender); + } + console.log(`\nFetching the fee payer account information.`); + const accountDetails = (await fetchAccount({ publicKey: sender })).account; + console.log( + `Using the fee payer account ${sender.toBase58()} with nonce: ${ + accountDetails?.nonce + } and balance: ${accountDetails?.balance}.` + ); + return { sender, senderKey }; +} + +async function tearDown() { + const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), + }); + if (keyPairReleaseMessage) console.info('\n' + keyPairReleaseMessage); +} + +function logNetworkConstants( + networkConstants: Mina.NetworkConstants | undefined +) { + console.log(`Account creation fee: ${networkConstants?.accountCreationFee}`); + console.log(`Slot time: ${networkConstants?.slotTime}`); + console.log(`Genesis timestamp: ${networkConstants?.genesisTimestamp}`); + console.log( + `Genesis date: ${new Date( + Number(networkConstants?.genesisTimestamp.toString() ?? '0') + )}` + ); +} diff --git a/src/examples/plain-html/index.html b/src/examples/plain-html/index.html index 0678fde35a..656bbec220 100644 --- a/src/examples/plain-html/index.html +++ b/src/examples/plain-html/index.html @@ -2,9 +2,9 @@ - hello-snarkyjs + hello-o1js diff --git a/src/examples/zkapps/dex/arbitrary_token_interaction.ts b/src/examples/zkapps/dex/arbitrary_token_interaction.ts index aaf7d81555..182beb95fa 100644 --- a/src/examples/zkapps/dex/arbitrary_token_interaction.ts +++ b/src/examples/zkapps/dex/arbitrary_token_interaction.ts @@ -4,7 +4,6 @@ import { TokenContract, addresses, keys, tokenIds } from './dex.js'; let doProofs = true; let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: userKey, publicKey: userAddress }] = Local.testAccounts; let tx; @@ -26,7 +25,9 @@ console.log('deploy & init token contracts...'); tx = await Mina.transaction(userAddress, () => { // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(userAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(1)); + feePayerUpdate.balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(1) + ); tokenX.deploy(); }); await tx.prove(); @@ -36,7 +37,9 @@ await tx.send(); console.log('arbitrary token minting...'); tx = await Mina.transaction(userAddress, () => { // pay fees for creating user's token X account - AccountUpdate.createSigned(userAddress).balance.subInPlace(accountFee.mul(1)); + AccountUpdate.createSigned(userAddress).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(1) + ); // 😈😈😈 mint any number of tokens to our account 😈😈😈 let tokenContract = new TokenContract(addresses.tokenX); tokenContract.token.mint({ diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 44ac6b5178..e4f963bb4f 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -394,7 +394,7 @@ class TokenContract extends SmartContract { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } /** @@ -410,7 +410,7 @@ class TokenContract extends SmartContract { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } // this is a very standardized deploy method. instead, we could also take the account update from a callback diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index b18ba43dea..9648640f8a 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -85,7 +85,7 @@ class TrivialCoin extends SmartContract implements Erc20 { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); // since this is the only method of this zkApp that resets the entire state, provedState: true implies // that this function was run. Since it can be run only once, this implies it was run exactly once diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index 16ea8fddcb..f7effd3360 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -19,7 +19,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: true, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -44,6 +43,7 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -61,7 +61,7 @@ tic('deploy dex contracts'); tx = await Mina.transaction(feePayerAddress, () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); dex.deploy(); dexTokenHolderX.deploy(); diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 4727851012..3922f82c39 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -15,7 +15,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -46,6 +45,7 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -63,7 +63,7 @@ tic('deploy dex contracts'); tx = await Mina.transaction(feePayerAddress, () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); dex.deploy(); dexTokenHolderX.deploy(); diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index e9f15fad22..bb50b2c119 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -9,7 +9,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -77,6 +76,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); @@ -110,7 +110,10 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 4); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees @@ -133,7 +136,10 @@ async function main({ withVesting }: { withVesting: boolean }) { // supply the initial liquidity where the token ratio can be arbitrary console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee, + }, () => { AccountUpdate.fundNewAccount(feePayerAddress); dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); @@ -437,7 +443,7 @@ async function main({ withVesting }: { withVesting: boolean }) { ); tx = await Mina.transaction(addresses.user2, () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); dex.redeemLiquidity(UInt64.from(USER_DL)); dex.redeemLiquidity(UInt64.from(USER_DL)); @@ -451,7 +457,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('user2 redeem liquidity'); tx = await Mina.transaction(addresses.user2, () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); dex.redeemLiquidity(UInt64.from(USER_DL)); }); diff --git a/src/examples/zkapps/dex/run_live.ts b/src/examples/zkapps/dex/run_live.ts index db9336fcc4..256ba5f87d 100644 --- a/src/examples/zkapps/dex/run_live.ts +++ b/src/examples/zkapps/dex/run_live.ts @@ -34,7 +34,6 @@ const network = Mina.Network({ lightnetAccountManager: 'http://localhost:8181', }); Mina.setActiveInstance(network); -let accountFee = Mina.accountCreationFee(); let tx, pendingTx: Mina.TransactionId, balances, oldBalances; @@ -74,6 +73,7 @@ let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); tx = await Mina.transaction(senderSpec, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(sender); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -97,7 +97,9 @@ if (successfulTransactions <= 1) { tic('deploy dex contracts'); tx = await Mina.transaction(senderSpec, () => { // pay fees for creating 3 dex accounts - AccountUpdate.createSigned(sender).balance.subInPlace(accountFee.mul(3)); + AccountUpdate.createSigned(sender).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(3) + ); dex.deploy(); dexTokenHolderX.deploy(); tokenX.approveUpdate(dexTokenHolderX.self); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 273431c903..bf44ac97e5 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -23,7 +23,6 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances; @@ -51,6 +50,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); @@ -227,7 +227,6 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -259,6 +258,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -309,10 +309,15 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { let feePayer = AccountUpdate.createSigned(feePayerAddress); - feePayer.balance.subInPlace(Mina.accountCreationFee().mul(4)); + feePayer.balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(4) + ); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity @@ -364,7 +369,10 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { AccountUpdate.fundNewAccount(feePayerAddress); modifiedDex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index b24b32354b..714fba42c5 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1273,7 +1273,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { let accountUpdate = AccountUpdate.createSigned(feePayer as PrivateKey); accountUpdate.label = 'AccountUpdate.fundNewAccount()'; - let fee = Mina.accountCreationFee(); + let fee = Mina.getNetworkConstants().accountCreationFee; numberOfAccounts ??= 1; if (typeof numberOfAccounts === 'number') fee = fee.mul(numberOfAccounts); else fee = fee.add(UInt64.from(numberOfAccounts.initialBalance ?? 0)); diff --git a/src/lib/caller.unit-test.ts b/src/lib/caller.unit-test.ts index 1c6508dcd6..47e242de6f 100644 --- a/src/lib/caller.unit-test.ts +++ b/src/lib/caller.unit-test.ts @@ -17,7 +17,7 @@ let parentId = TokenId.derive(publicKey); let tx = await Mina.transaction(privateKey, () => { let parent = AccountUpdate.defaultAccountUpdate(publicKey); parent.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; - parent.balance.subInPlace(Mina.accountCreationFee()); + parent.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); let child = AccountUpdate.defaultAccountUpdate(publicKey, parentId); child.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; diff --git a/src/lib/errors.ts b/src/lib/errors.ts index b7e65c0338..3fe584eac2 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -99,7 +99,7 @@ function handleResult(result: any) { * A list of keywords used to filter out unwanted lines from the error stack trace. */ const lineRemovalKeywords = [ - 'snarky_js_node.bc.cjs', + 'o1js_node.bc.cjs', '/builtin/', 'CatchAndPrettifyStacktrace', // Decorator name to remove from stacktrace (covers both class and method decorator) ] as const; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 88b87c7e87..f1d39ef8b8 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -19,6 +19,7 @@ import { export { fetchAccount, fetchLastBlock, + fetchGenesisConstants, checkZkappTransaction, parseFetchedAccount, markAccountToBeFetched, @@ -31,6 +32,7 @@ export { getCachedAccount, getCachedNetwork, getCachedActions, + getCachedGenesisConstants, addCachedAccount, networkConfig, setGraphqlEndpoint, @@ -44,7 +46,8 @@ export { removeJsonQuotes, fetchEvents, fetchActions, - Lightnet + Lightnet, + type GenesisConstants, }; type NetworkConfig = { @@ -216,6 +219,16 @@ type FetchError = { type ActionStatesStringified = { [K in keyof ActionStates]: string; }; +type GenesisConstants = { + genesisTimestamp: string; + coinbase: number; + accountCreationFee: number; + epochDuration: number; + k: number; + slotDuration: number; + slotsPerEpoch: number; +}; + // Specify 5min as the default timeout const defaultTimeout = 5 * 60 * 1000; @@ -257,6 +270,7 @@ let actionsToFetch = {} as Record< graphqlEndpoint: string; } >; +let genesisConstantsCache = {} as Record; function markAccountToBeFetched( publicKey: PublicKey, @@ -333,6 +347,7 @@ async function fetchMissingData( (async () => { try { await fetchLastBlock(graphqlEndpoint); + await fetchGenesisConstants(graphqlEndpoint); delete networksToFetch[network[0]]; } catch {} })() @@ -363,6 +378,12 @@ function getCachedActions( ?.actions; } +function getCachedGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): GenesisConstants { + return genesisConstantsCache[graphqlEndpoint]; +} + /** * Adds an account to the local cache, indexed by a GraphQL endpoint. */ @@ -819,6 +840,21 @@ const getActionsQuery = ( } }`; }; +const genesisConstantsQuery = `{ + genesisConstants { + genesisTimestamp + coinbase + accountCreationFee + } + daemonStatus { + consensusConfiguration { + epochDuration + k + slotDuration + slotsPerEpoch + } + } + }`; /** * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. @@ -1009,6 +1045,37 @@ async function fetchActions( return actionsList; } +/** + * Fetches genesis constants. + */ +async function fetchGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): Promise { + let [resp, error] = await makeGraphqlRequest( + genesisConstantsQuery, + graphqlEndpoint, + networkConfig.minaFallbackEndpoints + ); + if (error) throw Error(error.statusText); + const genesisConstants = resp?.data?.genesisConstants; + const consensusConfiguration = + resp?.data?.daemonStatus?.consensusConfiguration; + if (genesisConstants === undefined || consensusConfiguration === undefined) { + throw Error('Failed to fetch genesis constants.'); + } + const data = { + genesisTimestamp: genesisConstants.genesisTimestamp, + coinbase: Number(genesisConstants.coinbase), + accountCreationFee: Number(genesisConstants.accountCreationFee), + epochDuration: Number(consensusConfiguration.epochDuration), + k: Number(consensusConfiguration.k), + slotDuration: Number(consensusConfiguration.slotDuration), + slotsPerEpoch: Number(consensusConfiguration.slotsPerEpoch), + }; + genesisConstantsCache[graphqlEndpoint] = data; + return data as GenesisConstants; +} + namespace Lightnet { /** * Gets random key pair (public and private keys) from account manager diff --git a/src/lib/mina.ts b/src/lib/mina.ts index e3b5823a9c..bcc2573803 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -51,6 +51,7 @@ export { getAccount, hasAccount, getBalance, + getNetworkConstants, getNetworkState, accountCreationFee, sendTransaction, @@ -64,7 +65,9 @@ export { getProofsEnabled, // for internal testing only filterGroups, + type NetworkConstants }; + interface TransactionId { isSuccess: boolean; wait(options?: { maxAttempts?: number; interval?: number }): Promise; @@ -163,6 +166,15 @@ type ActionStates = { endActionState?: Field; }; +type NetworkConstants = { + genesisTimestamp: UInt64; + /** + * Duration of 1 slot in milliseconds + */ + slotTime: UInt64; + accountCreationFee: UInt64; +}; + function reportGetAccountError(publicKey: string, tokenId: string) { if (tokenId === TokenId.toBase58(TokenId.default)) { return `getAccount: Could not find account for public key ${publicKey}`; @@ -340,14 +352,10 @@ interface Mina { hasAccount(publicKey: PublicKey, tokenId?: Field): boolean; getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; - getNetworkConstants(): { - genesisTimestamp: UInt64; - /** - * Duration of 1 slot in millisecondw - */ - slotTime: UInt64; - accountCreationFee: UInt64; - }; + getNetworkConstants(): NetworkConstants; + /** + * @deprecated use {@link getNetworkConstants} + */ accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): Promise; fetchEvents: ( @@ -369,21 +377,23 @@ interface Mina { } const defaultAccountCreationFee = 1_000_000_000; +const defaultNetworkConstants: NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(defaultAccountCreationFee), +}; /** * A mock Mina blockchain running locally and useful for testing. */ function LocalBlockchain({ - accountCreationFee = defaultAccountCreationFee as string | number, proofsEnabled = true, enforceTransactionLimits = true, } = {}) { const slotTime = 3 * 60 * 1000; const startTime = Date.now(); const genesisTimestamp = UInt64.from(startTime); - const ledger = Ledger.create(); - let networkState = defaultNetworkState(); function addAccount(publicKey: PublicKey, balance: string) { @@ -412,12 +422,14 @@ function LocalBlockchain({ return { proofsEnabled, - accountCreationFee: () => UInt64.from(accountCreationFee), + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { return { + ...defaultNetworkConstants, genesisTimestamp, - accountCreationFee: UInt64.from(accountCreationFee), - slotTime: UInt64.from(slotTime), }; }, currentSlot() { @@ -490,7 +502,7 @@ function LocalBlockchain({ try { ledger.applyJsonTransaction( JSON.stringify(zkappCommandJson), - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); } catch (err: any) { @@ -499,7 +511,7 @@ function LocalBlockchain({ // TODO: label updates, and try to give precise explanations about what went wrong let errors = JSON.parse(err.message); err.message = invalidTransactionError(txn.transaction, errors, { - accountCreationFee, + accountCreationFee: defaultNetworkConstants.accountCreationFee.toString(), }); } finally { throw err; @@ -601,7 +613,7 @@ function LocalBlockchain({ applyJsonTransaction(json: string) { return ledger.applyJsonTransaction( json, - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); }, @@ -691,7 +703,6 @@ function Network( } | string ): Mina { - let accountCreationFee = UInt64.from(defaultAccountCreationFee); let minaGraphqlEndpoint: string; let archiveEndpoint: string; let lightnetAccountManagerEndpoint: string; @@ -737,22 +748,30 @@ function Network( ); } - // copied from mina/genesis_ledgers/berkeley.json - // TODO fetch from graphql instead of hardcoding - const genesisTimestampString = '2023-02-23T20:00:01Z'; - const genesisTimestamp = UInt64.from( - Date.parse(genesisTimestampString.slice(0, -1) + '+00:00') - ); - // TODO also fetch from graphql - const slotTime = UInt64.from(3 * 60 * 1000); return { - accountCreationFee: () => accountCreationFee, + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { - return { - genesisTimestamp, - slotTime, - accountCreationFee, - }; + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + return genesisConstants !== undefined + ? genesisToNetworkConstants(genesisConstants) + : defaultNetworkConstants; + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + if (genesisConstants !== undefined) + return genesisToNetworkConstants(genesisConstants); + } + return defaultNetworkConstants; }, currentSlot() { throw Error( @@ -814,7 +833,7 @@ function Network( if (network !== undefined) return network; } throw Error( - `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint}` + `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.` ); }, async sendTransaction(txn: Transaction) { @@ -994,9 +1013,12 @@ function BerkeleyQANet(graphqlEndpoint: string) { } let activeInstance: Mina = { - accountCreationFee: () => UInt64.from(defaultAccountCreationFee), + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { - throw new Error('must call Mina.setActiveInstance first'); + return defaultNetworkConstants; }, currentSlot: () => { throw new Error('must call Mina.setActiveInstance first'); @@ -1177,6 +1199,13 @@ function hasAccount(publicKey: PublicKey, tokenId?: Field): boolean { return activeInstance.hasAccount(publicKey, tokenId); } +/** + * @return Data associated with the current Mina network constants. + */ +function getNetworkConstants() { + return activeInstance.getNetworkConstants(); +} + /** * @return Data associated with the current state of the Mina network. */ @@ -1193,6 +1222,7 @@ function getBalance(publicKey: PublicKey, tokenId?: Field) { /** * Returns the default account creation fee. + * @deprecated use {@link Mina.getNetworkConstants} */ function accountCreationFee() { return activeInstance.accountCreationFee(); @@ -1592,3 +1622,15 @@ async function faucet(pub: PublicKey, network: string = 'berkeley-qanet') { } await waitForFunding(address); } + +function genesisToNetworkConstants( + genesisConstants: Fetch.GenesisConstants +): NetworkConstants { + return { + genesisTimestamp: UInt64.from( + Date.parse(genesisConstants.genesisTimestamp) + ), + slotTime: UInt64.from(genesisConstants.slotDuration), + accountCreationFee: UInt64.from(genesisConstants.accountCreationFee), + }; +} diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 25b10b9031..ac5fd8f8aa 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -58,7 +58,7 @@ function Network(accountUpdate: AccountUpdate): Network { }, requireEquals(value: UInt64) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let slot = timestampToGlobalSlot( value, `Timestamp precondition unsatisfied: the timestamp can only equal numbers of the form ${genesisTimestamp} + k*${slotTime},\n` + @@ -319,12 +319,12 @@ function getVariable( function globalSlotToTimestamp(slot: UInt32) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); return UInt64.from(slot).mul(slotTime).add(genesisTimestamp); } function timestampToGlobalSlot(timestamp: UInt64, message: string) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let { quotient: slot, rest } = timestamp .sub(genesisTimestamp) .divMod(slotTime); @@ -340,7 +340,7 @@ function timestampToGlobalSlotRange( // so we have to make the range smaller -- round up `tsLower` and round down `tsUpper` // also, we should clamp to the UInt32 max range [0, 2**32-1] let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let tsLowerInt = Int64.from(tsLower) .sub(genesisTimestamp) .add(slotTime) diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 038686ee32..5f8502f902 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -44,7 +44,7 @@ class TokenContract extends SmartContract { amount: this.SUPPLY, }); receiver.account.isNew.assertEquals(Bool(true)); - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); this.totalAmountInCirculation.set(this.SUPPLY.sub(100_000_000)); this.account.permissions.set({ ...Permissions.default(), @@ -172,7 +172,7 @@ async function setupLocal() { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer); feePayerUpdate.send({ to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + amount: Mina.getNetworkConstants().accountCreationFee, }); tokenZkapp.deploy(); }); @@ -189,7 +189,7 @@ async function setupLocalProofs() { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer, 3); feePayerUpdate.send({ to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + amount: Mina.getNetworkConstants().accountCreationFee, }); tokenZkapp.deploy(); tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!);