From 841a79c46ddf303331ee391456fbab64c0ef8a80 Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Thu, 11 Jan 2024 20:20:46 +0200 Subject: [PATCH 1/4] Use actual network genesis constants (timestamp). --- src/lib/fetch.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/mina.ts | 15 +++++++------- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 88b87c7e87..edda889e93 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -31,6 +31,7 @@ export { getCachedAccount, getCachedNetwork, getCachedActions, + getCachedGenesisConstants, addCachedAccount, networkConfig, setGraphqlEndpoint, @@ -216,6 +217,12 @@ type FetchError = { type ActionStatesStringified = { [K in keyof ActionStates]: string; }; +type GenesisConstants = { + genesisTimestamp: string; + coinbase: number; + accountCreationFee: number; +}; + // Specify 5min as the default timeout const defaultTimeout = 5 * 60 * 1000; @@ -257,6 +264,7 @@ let actionsToFetch = {} as Record< graphqlEndpoint: string; } >; +let genesisConstantsCache = {} as Record; function markAccountToBeFetched( publicKey: PublicKey, @@ -363,6 +371,23 @@ function getCachedActions( ?.actions; } +function getCachedGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): GenesisConstants { + let genesisConstants = genesisConstantsCache[graphqlEndpoint]; + if (genesisConstants === undefined) { + fetchGenesisConstants(graphqlEndpoint) + .then((fetchedGenesisConstants) => { + genesisConstantsCache[graphqlEndpoint] = fetchedGenesisConstants; + genesisConstants = fetchedGenesisConstants; + }) + .catch((error) => { + throw Error(error.message); + }); + } + return genesisConstants; +} + /** * Adds an account to the local cache, indexed by a GraphQL endpoint. */ @@ -819,6 +844,13 @@ const getActionsQuery = ( } }`; }; +const genesisConstantsQuery = `{ + genesisConstants { + genesisTimestamp + coinbase + accountCreationFee + } + }`; /** * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. @@ -1009,6 +1041,25 @@ 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; + if (genesisConstants === undefined) { + throw Error('Failed to fetch genesis constants.'); + } + return genesisConstants 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..d240dd134d 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -343,7 +343,7 @@ interface Mina { getNetworkConstants(): { genesisTimestamp: UInt64; /** - * Duration of 1 slot in millisecondw + * Duration of 1 slot in milliseconds */ slotTime: UInt64; accountCreationFee: UInt64; @@ -737,19 +737,18 @@ 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, getNetworkConstants() { return { - genesisTimestamp, + genesisTimestamp: UInt64.from( + Date.parse( + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint) + .genesisTimestamp + ) + ), slotTime, accountCreationFee, }; From ae52dc11e10f1885824726243b14f9857dd9226f Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Sat, 13 Jan 2024 13:48:21 +0200 Subject: [PATCH 2/4] Refactoring. --- run-ci-live-tests.sh | 9 +++++ src/examples/fetch_live.ts | 73 ++++++++++++++++++++++++++++++++++++++ src/lib/fetch.ts | 46 ++++++++++++++++-------- src/lib/mina.ts | 70 +++++++++++++++++++++++++----------- 4 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 src/examples/fetch_live.ts 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/examples/fetch_live.ts b/src/examples/fetch_live.ts new file mode 100644 index 0000000000..f42b187504 --- /dev/null +++ b/src/examples/fetch_live.ts @@ -0,0 +1,73 @@ +import { expect } from 'expect'; +import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; + +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +const minaGraphQlEndpoint = useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://berkeley.minascan.io/graphql'; +const network = Mina.Network({ + mina: minaGraphQlEndpoint, + archive: useCustomLocalNetwork + ? 'http://localhost:8282' + : 'https://api.minascan.io/archive/berkeley/v1/graphql', + lightnetAccountManager: 'http://localhost:8181', +}); +Mina.setActiveInstance(network); +const transactionFee = 100_000_000; + +// Fee payer setup +console.log(''); +const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); +const sender = senderKey.toPublicKey(); +if (!useCustomLocalNetwork) { + console.log(`Funding the fee payer account.`); + await Mina.faucet(sender); +} +console.log(`Fetching 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}.` +); + +console.log(''); +console.log( + "Check that network constants CAN'T be fetched outside of a transaction." +); +const getNetworkConstants = () => { + Mina.activeInstance.getNetworkConstants(); +}; +expect(getNetworkConstants).toThrow( + `getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphQlEndpoint} outside of a transaction.` +); + +console.log(''); +console.log( + 'Check that network constants CAN be fetched within the transaction.' +); +let slotTime: UInt64 | undefined; +let genesisTimestamp: UInt64 | undefined; +await Mina.transaction({ sender, fee: transactionFee }, () => { + const networkConstants = Mina.activeInstance.getNetworkConstants(); + slotTime = networkConstants.slotTime; + genesisTimestamp = networkConstants.genesisTimestamp; +}); + +expect(slotTime).not.toBeUndefined(); +expect(genesisTimestamp).not.toBeUndefined(); + +console.log(`Slot time: ${slotTime}`); +console.log(`Genesis timestamp: ${genesisTimestamp}`); +console.log( + `Genesis date: ${new Date(Number(genesisTimestamp?.toString() ?? '0'))}` +); + +// Tear down +console.log(''); +const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), +}); +if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index edda889e93..f1d39ef8b8 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -19,6 +19,7 @@ import { export { fetchAccount, fetchLastBlock, + fetchGenesisConstants, checkZkappTransaction, parseFetchedAccount, markAccountToBeFetched, @@ -45,7 +46,8 @@ export { removeJsonQuotes, fetchEvents, fetchActions, - Lightnet + Lightnet, + type GenesisConstants, }; type NetworkConfig = { @@ -221,6 +223,10 @@ type GenesisConstants = { genesisTimestamp: string; coinbase: number; accountCreationFee: number; + epochDuration: number; + k: number; + slotDuration: number; + slotsPerEpoch: number; }; // Specify 5min as the default timeout @@ -341,6 +347,7 @@ async function fetchMissingData( (async () => { try { await fetchLastBlock(graphqlEndpoint); + await fetchGenesisConstants(graphqlEndpoint); delete networksToFetch[network[0]]; } catch {} })() @@ -374,18 +381,7 @@ function getCachedActions( function getCachedGenesisConstants( graphqlEndpoint = networkConfig.minaEndpoint ): GenesisConstants { - let genesisConstants = genesisConstantsCache[graphqlEndpoint]; - if (genesisConstants === undefined) { - fetchGenesisConstants(graphqlEndpoint) - .then((fetchedGenesisConstants) => { - genesisConstantsCache[graphqlEndpoint] = fetchedGenesisConstants; - genesisConstants = fetchedGenesisConstants; - }) - .catch((error) => { - throw Error(error.message); - }); - } - return genesisConstants; + return genesisConstantsCache[graphqlEndpoint]; } /** @@ -850,6 +846,14 @@ const genesisConstantsQuery = `{ coinbase accountCreationFee } + daemonStatus { + consensusConfiguration { + epochDuration + k + slotDuration + slotsPerEpoch + } + } }`; /** @@ -1054,10 +1058,22 @@ async function fetchGenesisConstants( ); if (error) throw Error(error.statusText); const genesisConstants = resp?.data?.genesisConstants; - if (genesisConstants === undefined) { + const consensusConfiguration = + resp?.data?.daemonStatus?.consensusConfiguration; + if (genesisConstants === undefined || consensusConfiguration === undefined) { throw Error('Failed to fetch genesis constants.'); } - return genesisConstants as GenesisConstants; + 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 { diff --git a/src/lib/mina.ts b/src/lib/mina.ts index d240dd134d..c984ee174c 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -65,6 +65,7 @@ export { // for internal testing only filterGroups, }; + interface TransactionId { isSuccess: boolean; wait(options?: { maxAttempts?: number; interval?: number }): Promise; @@ -163,6 +164,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 +350,7 @@ interface Mina { hasAccount(publicKey: PublicKey, tokenId?: Field): boolean; getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; - getNetworkConstants(): { - genesisTimestamp: UInt64; - /** - * Duration of 1 slot in milliseconds - */ - slotTime: UInt64; - accountCreationFee: UInt64; - }; + getNetworkConstants(): NetworkConstants; accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): Promise; fetchEvents: ( @@ -369,6 +372,11 @@ interface Mina { } const defaultAccountCreationFee = 1_000_000_000; +const defaultNetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(defaultAccountCreationFee), +}; /** * A mock Mina blockchain running locally and useful for testing. @@ -737,21 +745,29 @@ function Network( ); } - // TODO also fetch from graphql - const slotTime = UInt64.from(3 * 60 * 1000); return { accountCreationFee: () => accountCreationFee, getNetworkConstants() { - return { - genesisTimestamp: UInt64.from( - Date.parse( - Fetch.getCachedGenesisConstants(minaGraphqlEndpoint) - .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); + } + throw Error( + `getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.` + ); }, currentSlot() { throw Error( @@ -813,7 +829,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) { @@ -1591,3 +1607,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), + }; +} From e565337e083f024552cbc781a6f0f17661ebcd4b Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Sat, 20 Jan 2024 14:11:22 +0200 Subject: [PATCH 3/4] Refactoring. --- src/examples/fetch_live.ts | 141 ++++++++++-------- .../zkapps/dex/arbitrary_token_interaction.ts | 9 +- src/examples/zkapps/dex/dex.ts | 4 +- src/examples/zkapps/dex/erc20.ts | 2 +- .../zkapps/dex/happy-path-with-actions.ts | 4 +- .../zkapps/dex/happy-path-with-proofs.ts | 4 +- src/examples/zkapps/dex/run.ts | 16 +- src/examples/zkapps/dex/run_live.ts | 6 +- src/examples/zkapps/dex/upgradability.ts | 18 ++- src/lib/account_update.ts | 2 +- src/lib/caller.unit-test.ts | 2 +- src/lib/mina.ts | 49 +++--- src/lib/precondition.ts | 8 +- src/lib/token.test.ts | 6 +- 14 files changed, 162 insertions(+), 109 deletions(-) diff --git a/src/examples/fetch_live.ts b/src/examples/fetch_live.ts index f42b187504..85ce4b8d4d 100644 --- a/src/examples/fetch_live.ts +++ b/src/examples/fetch_live.ts @@ -2,72 +2,91 @@ import { expect } from 'expect'; import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; -const minaGraphQlEndpoint = useCustomLocalNetwork - ? 'http://localhost:8080/graphql' - : 'https://berkeley.minascan.io/graphql'; -const network = Mina.Network({ - mina: minaGraphQlEndpoint, - archive: useCustomLocalNetwork - ? 'http://localhost:8282' - : 'https://api.minascan.io/archive/berkeley/v1/graphql', - lightnetAccountManager: 'http://localhost:8181', -}); -Mina.setActiveInstance(network); +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(); -// Fee payer setup -console.log(''); -const senderKey = useCustomLocalNetwork - ? (await Lightnet.acquireKeyPair()).privateKey - : PrivateKey.random(); -const sender = senderKey.toPublicKey(); -if (!useCustomLocalNetwork) { - console.log(`Funding the fee payer account.`); - await Mina.faucet(sender); +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); } -console.log(`Fetching 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}.` -); -console.log(''); -console.log( - "Check that network constants CAN'T be fetched outside of a transaction." -); -const getNetworkConstants = () => { - Mina.activeInstance.getNetworkConstants(); -}; -expect(getNetworkConstants).toThrow( - `getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphQlEndpoint} outside of a transaction.` -); +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); +} -console.log(''); -console.log( - 'Check that network constants CAN be fetched within the transaction.' -); -let slotTime: UInt64 | undefined; -let genesisTimestamp: UInt64 | undefined; -await Mina.transaction({ sender, fee: transactionFee }, () => { - const networkConstants = Mina.activeInstance.getNetworkConstants(); - slotTime = networkConstants.slotTime; - genesisTimestamp = networkConstants.genesisTimestamp; -}); +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', + }); +} -expect(slotTime).not.toBeUndefined(); -expect(genesisTimestamp).not.toBeUndefined(); +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 }; +} -console.log(`Slot time: ${slotTime}`); -console.log(`Genesis timestamp: ${genesisTimestamp}`); -console.log( - `Genesis date: ${new Date(Number(genesisTimestamp?.toString() ?? '0'))}` -); +async function tearDown() { + const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), + }); + if (keyPairReleaseMessage) console.info('\n' + keyPairReleaseMessage); +} -// Tear down -console.log(''); -const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ - publicKey: sender.toBase58(), -}); -if (keyPairReleaseMessage) console.info(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/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 8bf5a1ea8f..0db07a690a 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 23f5e66c08..1077a0ae36 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1247,7 +1247,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/mina.ts b/src/lib/mina.ts index c984ee174c..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,6 +65,7 @@ export { getProofsEnabled, // for internal testing only filterGroups, + type NetworkConstants }; interface TransactionId { @@ -351,6 +353,9 @@ interface Mina { getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; getNetworkConstants(): NetworkConstants; + /** + * @deprecated use {@link getNetworkConstants} + */ accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): Promise; fetchEvents: ( @@ -372,7 +377,7 @@ interface Mina { } const defaultAccountCreationFee = 1_000_000_000; -const defaultNetworkConstants = { +const defaultNetworkConstants: NetworkConstants = { genesisTimestamp: UInt64.from(0), slotTime: UInt64.from(3 * 60 * 1000), accountCreationFee: UInt64.from(defaultAccountCreationFee), @@ -382,16 +387,13 @@ const defaultNetworkConstants = { * 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) { @@ -420,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() { @@ -498,7 +502,7 @@ function LocalBlockchain({ try { ledger.applyJsonTransaction( JSON.stringify(zkappCommandJson), - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); } catch (err: any) { @@ -507,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; @@ -609,7 +613,7 @@ function LocalBlockchain({ applyJsonTransaction(json: string) { return ledger.applyJsonTransaction( json, - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); }, @@ -699,7 +703,6 @@ function Network( } | string ): Mina { - let accountCreationFee = UInt64.from(defaultAccountCreationFee); let minaGraphqlEndpoint: string; let archiveEndpoint: string; let lightnetAccountManagerEndpoint: string; @@ -746,7 +749,10 @@ function Network( } return { - accountCreationFee: () => accountCreationFee, + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { if (currentTransaction()?.fetchMode === 'test') { Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); @@ -765,9 +771,7 @@ function Network( if (genesisConstants !== undefined) return genesisToNetworkConstants(genesisConstants); } - throw Error( - `getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.` - ); + return defaultNetworkConstants; }, currentSlot() { throw Error( @@ -1009,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'); @@ -1192,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. */ @@ -1208,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(); 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 a8b6428af1..6f711f0a6a 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!); From 0cdac60734d1c03343c0707bb422253662dcd00f Mon Sep 17 00:00:00 2001 From: Serhii Shymkiv Date: Sun, 21 Jan 2024 10:25:55 +0200 Subject: [PATCH 4/4] Changelog entry. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd76e3380e..e8f5d6c62f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/08ba27329...HEAD) +### 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. + ## [0.15.2](https://github.com/o1-labs/o1js/compare/1ad7333e9e...08ba27329) ### Fixed