|
| 1 | +import { |
| 2 | + ContractOperationVersion, |
| 3 | + ContractOperationVersionedVerifierKey, |
| 4 | + VerifierKeyInsert, |
| 5 | + VerifierKeyRemove, |
| 6 | +} from '@midnight-ntwrk/ledger-v8'; |
| 7 | +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; |
| 8 | +import { submitRawMaintenanceUpdate } from '../../_harness/cma.js'; |
| 9 | +import { |
| 10 | + deployTestTokenV1, |
| 11 | + type TestTokenV1Kit, |
| 12 | +} from '../../fixtures/testTokenV1.js'; |
| 13 | + |
| 14 | +/** |
| 15 | + * Spec: bundle-shape matrix for non-`ReplaceAuthority` `MaintenanceUpdate`s. |
| 16 | + * |
| 17 | + * What we know going in (from earlier specs): |
| 18 | + * - Multi-`ReplaceAuthority` in one bundle: rejected at submission ([`multiUpdate.spec.ts`](./multiUpdate.spec.ts) Q2). |
| 19 | + * - Multi-`VerifierKeyInsert` on the **same** op: tx finalises but the |
| 20 | + * bundle reverts atomically with `status: 'FailFallible'` |
| 21 | + * ([`multiUpdate.spec.ts`](./multiUpdate.spec.ts) Q4 chain-level). |
| 22 | + * - `ReplaceAuthority` mixed with another `SingleUpdate` kind: rejected |
| 23 | + * in either order ([`mixedBundle.spec.ts`](./mixedBundle.spec.ts) Q7). |
| 24 | + * |
| 25 | + * What this spec fills in: the three bundle shapes that remain — VK-only |
| 26 | + * bundles on **different** operations. These are the realistic happy path |
| 27 | + * for a multi-circuit version bump (e.g., simultaneously rotating `_mint` |
| 28 | + * and `pause` VKs in one tx). The suite has implicitly assumed they work |
| 29 | + * but never directly confirmed it. |
| 30 | + * |
| 31 | + * Three describes, each its own fresh deploy (the bundles mutate state |
| 32 | + * and we want each test in a known-clean starting state): |
| 33 | + * |
| 34 | + * 1. **Multi-insert on different ops** — `[Insert(_mint), Insert(pause)]` |
| 35 | + * against empty slots. Expect entire success. |
| 36 | + * 2. **Multi-remove on different ops** — `[Remove(_mint), Remove(pause)]` |
| 37 | + * against occupied slots. Expect entire success. |
| 38 | + * 3. **Mixed `Insert` + `Remove` on different ops** — `[Insert(_mint), |
| 39 | + * Remove(pause)]`. Q7 only forbade mixing with `ReplaceAuthority`; |
| 40 | + * mixing VK kinds should be allowed. Expect entire success. |
| 41 | + * |
| 42 | + * If any describe fails, we pin to the observed behaviour and update the |
| 43 | + * [README notes table](../../README.md#notes--open-questions). Until then, |
| 44 | + * Q10 (this whole probe) is the spec's contribution. |
| 45 | + */ |
| 46 | +describe('TestToken — multi-VK bundles on different ops', () => { |
| 47 | + describe('multi-insert on different empty slots', () => { |
| 48 | + let v1: TestTokenV1Kit; |
| 49 | + |
| 50 | + beforeAll(async () => { |
| 51 | + v1 = await deployTestTokenV1(); |
| 52 | + // Empty both target slots so the inserts land cleanly. |
| 53 | + await v1.deployed.circuitMaintenanceTx._mint.removeVerifierKey(); |
| 54 | + await v1.deployed.circuitMaintenanceTx.pause.removeVerifierKey(); |
| 55 | + }); |
| 56 | + |
| 57 | + afterAll(async () => { |
| 58 | + await v1?.teardown(); |
| 59 | + }); |
| 60 | + |
| 61 | + it('should accept the bundle entirely; both slots become occupied', async () => { |
| 62 | + const mintVk = await v1.providers.zkConfigProvider.getVerifierKey('_mint'); |
| 63 | + const pauseVk = await v1.providers.zkConfigProvider.getVerifierKey('pause'); |
| 64 | + const versionedMintVk = new ContractOperationVersionedVerifierKey('v3', mintVk); |
| 65 | + const versionedPauseVk = new ContractOperationVersionedVerifierKey('v3', pauseVk); |
| 66 | + |
| 67 | + const result = await submitRawMaintenanceUpdate( |
| 68 | + v1.providers, |
| 69 | + v1.contractAddress, |
| 70 | + [ |
| 71 | + new VerifierKeyInsert('_mint', versionedMintVk), |
| 72 | + new VerifierKeyInsert('pause', versionedPauseVk), |
| 73 | + ], |
| 74 | + ); |
| 75 | + expect(result.status).toBe('SucceedEntirely'); |
| 76 | + |
| 77 | + const stateAfter = await v1.providers.publicDataProvider.queryContractState( |
| 78 | + v1.contractAddress, |
| 79 | + ); |
| 80 | + expect(stateAfter?.operation('_mint')).toBeDefined(); |
| 81 | + expect(stateAfter?.operation('pause')).toBeDefined(); |
| 82 | + }); |
| 83 | + }); |
| 84 | + |
| 85 | + describe('multi-remove on different occupied slots', () => { |
| 86 | + let v1: TestTokenV1Kit; |
| 87 | + |
| 88 | + beforeAll(async () => { |
| 89 | + // Fresh deploy: both slots are occupied with their original VKs. |
| 90 | + v1 = await deployTestTokenV1(); |
| 91 | + }); |
| 92 | + |
| 93 | + afterAll(async () => { |
| 94 | + await v1?.teardown(); |
| 95 | + }); |
| 96 | + |
| 97 | + it('should accept the bundle entirely; both slots become empty', async () => { |
| 98 | + const v3 = new ContractOperationVersion('v3'); |
| 99 | + const result = await submitRawMaintenanceUpdate( |
| 100 | + v1.providers, |
| 101 | + v1.contractAddress, |
| 102 | + [ |
| 103 | + new VerifierKeyRemove('_mint', v3), |
| 104 | + new VerifierKeyRemove('pause', v3), |
| 105 | + ], |
| 106 | + ); |
| 107 | + expect(result.status).toBe('SucceedEntirely'); |
| 108 | + |
| 109 | + const stateAfter = await v1.providers.publicDataProvider.queryContractState( |
| 110 | + v1.contractAddress, |
| 111 | + ); |
| 112 | + expect(stateAfter?.operation('_mint')).toBeUndefined(); |
| 113 | + expect(stateAfter?.operation('pause')).toBeUndefined(); |
| 114 | + }); |
| 115 | + }); |
| 116 | + |
| 117 | + describe('mixed Insert + Remove on different ops', () => { |
| 118 | + let v1: TestTokenV1Kit; |
| 119 | + |
| 120 | + beforeAll(async () => { |
| 121 | + v1 = await deployTestTokenV1(); |
| 122 | + // Empty `_mint` so the bundle's Insert can land; leave `pause` |
| 123 | + // occupied so the bundle's Remove has something to remove. |
| 124 | + await v1.deployed.circuitMaintenanceTx._mint.removeVerifierKey(); |
| 125 | + }); |
| 126 | + |
| 127 | + afterAll(async () => { |
| 128 | + await v1?.teardown(); |
| 129 | + }); |
| 130 | + |
| 131 | + it('should accept the bundle entirely; `_mint` becomes occupied, `pause` becomes empty', async () => { |
| 132 | + const mintVk = await v1.providers.zkConfigProvider.getVerifierKey('_mint'); |
| 133 | + const versionedMintVk = new ContractOperationVersionedVerifierKey('v3', mintVk); |
| 134 | + const v3 = new ContractOperationVersion('v3'); |
| 135 | + |
| 136 | + const result = await submitRawMaintenanceUpdate( |
| 137 | + v1.providers, |
| 138 | + v1.contractAddress, |
| 139 | + [ |
| 140 | + new VerifierKeyInsert('_mint', versionedMintVk), |
| 141 | + new VerifierKeyRemove('pause', v3), |
| 142 | + ], |
| 143 | + ); |
| 144 | + expect(result.status).toBe('SucceedEntirely'); |
| 145 | + |
| 146 | + const stateAfter = await v1.providers.publicDataProvider.queryContractState( |
| 147 | + v1.contractAddress, |
| 148 | + ); |
| 149 | + expect(stateAfter?.operation('_mint')).toBeDefined(); |
| 150 | + expect(stateAfter?.operation('pause')).toBeUndefined(); |
| 151 | + }); |
| 152 | + }); |
| 153 | +}); |
0 commit comments