diff --git a/CHANGELOG.md b/CHANGELOG.md index da97589a3..e5e1272dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/e1bac02...HEAD) + +### Added + +- Add `enforceTransactionLimits` parameter on Network https://github.com/o1-labs/o1js/issues/1910 + ### Fixed - Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906 diff --git a/src/lib/mina/mina.network.unit-test.ts b/src/lib/mina/mina.network.unit-test.ts new file mode 100644 index 000000000..73b2a0e1d --- /dev/null +++ b/src/lib/mina/mina.network.unit-test.ts @@ -0,0 +1,202 @@ +import { + State, + state, + UInt64, + Bool, + SmartContract, + Mina, + AccountUpdate, + method, + PublicKey, + Permissions, + VerificationKey, + Field, + Int64, + TokenId, + TokenContract as TokenContractBase, + AccountUpdateForest, + PrivateKey, +} from 'o1js'; +import { test, describe, it, before } from 'node:test'; +import { expect } from 'expect'; + + + +const defaultNetwork = Mina.Network({ + networkId: "testnet", + mina: "https://example.com/graphql", + archive: "https://example.com//graphql" +}); + +const enforcedNetwork = Mina.Network({ + networkId: "testnet", + mina: "https://example.com/graphql", + archive: "https://example.com//graphql", + bypassTransactionLimits: false +}); + +const unlimitedNetwork = Mina.Network({ + networkId: "testnet", + mina: "https://unlimited.com/graphql", + archive: "https://unlimited.com//graphql", + bypassTransactionLimits: true +}); + +describe('Test default network', () => { + let bobAccount: PublicKey, + bobKey: PrivateKey; + + before(async () => { + + Mina.setActiveInstance(defaultNetwork); + bobKey = PrivateKey.random(); + bobAccount = bobKey.toPublicKey(); + }); + + + it('Simple account update', async () => { + + let txn = await Mina.transaction(async () => { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('Multiple account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 2; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('More than limit account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 12; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + // failure with default bypassTransactionLimits value + await expect(txn.sign([bobKey]).safeSend()).rejects.toThrow(); + }); +}); + +describe('Test enforced network', () => { + let bobAccount: PublicKey, + bobKey: PrivateKey; + + before(async () => { + + Mina.setActiveInstance(enforcedNetwork); + bobKey = PrivateKey.random(); + bobAccount = bobKey.toPublicKey(); + }); + + + it('Simple account update', async () => { + + let txn = await Mina.transaction(async () => { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('Multiple account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 2; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('More than limit account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 12; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + // failure with bypassTransactionLimits = false + await expect(txn.sign([bobKey]).safeSend()).rejects.toThrow(); + }); +}); + +describe('Test unlimited network', () => { + let bobAccount: PublicKey, + bobKey: PrivateKey; + + before(async () => { + + Mina.setActiveInstance(unlimitedNetwork); + bobKey = PrivateKey.random(); + bobAccount = bobKey.toPublicKey(); + }); + + + it('Simple account update', async () => { + + let txn = await Mina.transaction(async () => { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(1)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('Multiple account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 2; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + await txn.sign([bobKey]).safeSend(); + + }); + + it('More than limit account update', async () => { + + let txn = await Mina.transaction(async () => { + for (let index = 0; index < 12; index++) { + const accountUpdateBob = AccountUpdate.create(bobAccount, Field.from(index)); + accountUpdateBob.account.balance.requireEquals(UInt64.zero); + accountUpdateBob.balance.addInPlace(UInt64.one); + } + }); + await txn.prove(); + // success with bypassTransactionLimits = true + await txn.sign([bobKey]).safeSend(); + }); +}); \ No newline at end of file diff --git a/src/lib/mina/mina.ts b/src/lib/mina/mina.ts index 2dc41d542..67edd659e 100644 --- a/src/lib/mina/mina.ts +++ b/src/lib/mina/mina.ts @@ -104,21 +104,24 @@ function Network(options: { mina: string | string[]; archive?: string | string[]; lightnetAccountManager?: string; + bypassTransactionLimits?: boolean; }): Mina; function Network( options: | { - networkId?: NetworkId; - mina: string | string[]; - archive?: string | string[]; - lightnetAccountManager?: string; - } + networkId?: NetworkId; + mina: string | string[]; + archive?: string | string[]; + lightnetAccountManager?: string; + bypassTransactionLimits?: boolean; + } | string ): Mina { let minaNetworkId: NetworkId = 'testnet'; let minaGraphqlEndpoint: string; let archiveEndpoint: string; let lightnetAccountManagerEndpoint: string; + let enforceTransactionLimits: boolean = true; if (options && typeof options === 'string') { minaGraphqlEndpoint = options; @@ -158,6 +161,11 @@ function Network( lightnetAccountManagerEndpoint = options.lightnetAccountManager; Fetch.setLightnetAccountManagerEndpoint(lightnetAccountManagerEndpoint); } + + if (options.bypassTransactionLimits !== undefined && + typeof options.bypassTransactionLimits === 'boolean') { + enforceTransactionLimits = !options.bypassTransactionLimits; + } } else { throw new Error( "Network: malformed input. Please provide a string or an object with 'mina' and 'archive' endpoints." @@ -251,7 +259,7 @@ function Network( }, sendTransaction(txn) { return toPendingTransactionPromise(async () => { - verifyTransactionLimits(txn.transaction); + if (enforceTransactionLimits) verifyTransactionLimits(txn.transaction); let [response, error] = await Fetch.sendZkapp(txn.toJSON()); let errors: string[] = [];