import { AutoVerifier } from '../../webapp/auto-verifier'; async function sleep(ms: number) { if (ms === 0) return; return new Promise((resolve) => { setTimeout(resolve, ms); }); } // Disjoint the current async function to allow other promises to be handled before the next lines are run async function disjoint() { await sleep(0); } type QueryablePromise = Promise & { isPending: () => boolean, isRejected: () => boolean, isFulfilled: () => boolean }; /** * This function allow you to modify a JS Promise by adding some status properties. * Based on: http://stackoverflow.com/questions/21485545/is-there-a-way-to-tell-if-an-es6-promise-is-fulfilled-rejected-resolved * But modified according to the specs of promises : https://promisesaplus.com/ */ function makeQuerablePromise(promise: Promise) { // Set initial state var isPending = true; var isRejected = false; var isFulfilled = false; // Observe the promise, saving the fulfillment in a closure scope. var result = promise.then( function(v) { isFulfilled = true; isPending = false; return v; }, function(e) { isRejected = true; isPending = false; throw e; } ); (result as any).isFulfilled = function() { return isFulfilled; }; (result as any).isPending = function() { return isPending; }; (result as any).isRejected = function() { return isRejected; }; return result as QueryablePromise; } class AutoVerifierTest { static createSimple(primaryResult: T, trustedResult: T): AutoVerifier { return new AutoVerifier( async () => { return primaryResult; }, async () => { return trustedResult; }, async (primaryResult: T | null, trustedResult: T | null) => {} ); } static createPromises(primaryResult: T | null, trustedResult: T | null, primaryDelay: number, trustedDelay: number, verifyDelay: number, primaryFail: boolean = false, trustedFail: boolean = false): { primaryPromise: QueryablePromise, // resolves BEFORE the function returns trustedPromise: QueryablePromise, // resolves BEFORE the function returns verifyPromise: QueryablePromise<{ primaryResult: T | null, trustedResult: T | null }>, // resolves AFTER function returns verifier: AutoVerifier } { let primaryResolve = (value: T | null) => {}; let trustedResolve = (value: T | null) => {}; let verifyResolve = (value: { primaryResult: T | null, trustedResult: T | null }) => {}; let primaryPromise = new Promise((resolve, reject) => { primaryResolve = resolve; }); let trustedPromise = new Promise((resolve, reject) => { trustedResolve = resolve; }); let verifyPromise = new Promise<{ primaryResult: T | null, trustedResult: T | null }>((resolve) => { verifyResolve = resolve; }); let verifier = new AutoVerifier( async () => { await sleep(primaryDelay); if (primaryFail) { primaryResolve(null); throw new Error('primary reject'); } else { let result = primaryResult; primaryResolve(result); await disjoint(); // if you need to know what this does you will not be happy :) return result; } }, async () => { await sleep(trustedDelay); if (trustedFail) { trustedResolve(null); throw new Error('trusted reject'); } else { let result = trustedResult; trustedResolve(result); await disjoint(); return result; } }, async (primaryResult: T | null, trustedResult: T | null) => { await sleep(verifyDelay); if (primaryResult !== null && trustedResult !== null && primaryResult != trustedResult) verifier.unverify(); // auto-unverify if not equal (async () => { await disjoint(); verifyResolve({ primaryResult, trustedResult }); })(); } ); return { primaryPromise: makeQuerablePromise(primaryPromise), trustedPromise: makeQuerablePromise(trustedPromise), verifyPromise: makeQuerablePromise(verifyPromise), verifier }; } } describe('basic functionality', () => { test('basic functionality', async () => { const verifier = AutoVerifierTest.createSimple(2, 2); expect(verifier.trustedPromise === null); expect(verifier.trustedStatus === 'none'); let result = await verifier.fetchAndVerifyIfNeeded(); expect(result).toBe(2); }); }); describe('promise order tests', () => { test('normal verification', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(2); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); }); test('odd-ordered verification', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); await fetchPromise; expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; // note: this is already fulfilled expect(trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); }); test('normal trusted override', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(null); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(3); expect(fetchPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(null); expect(verification.trustedResult).toBe(3); expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise is called AFTER expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); // does not get auto unverified let result = await fetchPromise; expect(result).toBe(3); }); test('normal invalidation', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(3); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(3); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('none'); // gets auto unverified }); test('odd-ordered invalidation', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 65, 35, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; // note: this is already fulfilled expect(trustedResult).toBe(3); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(3); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('none'); // gets auto unverified }); test('normal unverification during primary func', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(2); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); }); test('odd-ordered unverification during primary func', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 65, 35, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(true); // since 65ms > 35ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); verifier.unverify(); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let trustedResult = await trustedPromise; // note: this is already fulfilled expect(trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(2); expect(verification.trustedResult).toBe(2); expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); }); test('normal unverification during trusted func', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); await fetchPromise; expect(fetchPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); verifier.unverify(); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(2); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('none'); // verification will not happen since the verifier.trustedPromise is null and we already returned }); test('normal unverification during primary func with trusted override', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(null); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); verifier.unverify(); // keep in mind this is still during the primary func since this is before we await the fetchPromise expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('verifying'); let verification = await verifyPromise; expect(verification.primaryResult).toBe(null); expect(verification.trustedResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(true); // since verifyPromise occurs AFTER it returns expect(verifyPromise.isFulfilled()).toBe(true); expect(verifier.trustedStatus).toBe('verified'); let fetchResult = await fetchPromise; expect(fetchResult).toBe(2); expect(fetchPromise.isFulfilled()).toBe(true); }); test('normal unverification during trusted func with trusted override', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15); expect(primaryPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(false); expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); let fetchPromise = makeQuerablePromise(verifier.fetchAndVerifyIfNeeded()); expect(verifier.trustedPromise !== null); expect(verifier.trustedStatus === 'fetching'); let primaryResult = await primaryPromise; expect(primaryResult).toBe(null); expect(fetchPromise.isFulfilled()).toBe(false); expect(primaryPromise.isFulfilled()).toBe(true); expect(trustedPromise.isFulfilled()).toBe(false); // since 35ms < 65ms expect(verifyPromise.isFulfilled()).toBe(false); expect(verifier.trustedStatus).toBe('fetching'); let trustedResult = await trustedPromise; expect(trustedResult).toBe(2); verifier.unverify(); // keep in mind this is still during the trusted func since this is before we await the fetchPromise expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); expect(fetchPromise.isFulfilled()).toBe(false); expect(trustedPromise.isFulfilled()).toBe(true); expect(verifyPromise.isFulfilled()).toBe(false); // this will resolve with null since we got unverified during the fetch let fetchResult = await fetchPromise; expect(fetchResult).toBe(null); expect(fetchPromise.isFulfilled()).toBe(true); }); }); describe('error tests', () => { test('primary error', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, false); expect.assertions(3); try { await verifier.fetchAndVerifyIfNeeded(); } catch (e) { expect(e).toBeDefined(); } await primaryPromise; await trustedPromise; await disjoint(); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); }); test('trusted error thrown out', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, false, true); await verifier.fetchAndVerifyIfNeeded(); await primaryPromise; await trustedPromise; await disjoint(); await disjoint(); // :D since trustedPromise happens BEFORE expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); }); test('trusted error considered since null primary result', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 2, 35, 65, 15, false, true); expect.assertions(3); try { await verifier.fetchAndVerifyIfNeeded(); } catch (e) { expect(e).toBeDefined(); } await primaryPromise; await trustedPromise; await disjoint(); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); }); test('both error only one exception', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 2, 35, 65, 15, true, true); expect.assertions(3); try { await verifier.fetchAndVerifyIfNeeded(); } catch (e) { expect(e).toBeDefined(); } await primaryPromise; await trustedPromise; await disjoint(); expect(verifier.trustedPromise).toBe(null); expect(verifier.trustedStatus).toBe('none'); }); }); describe('deduplication', () => { test('primary deduplication', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(2, 3, 35, 65, 15); await Promise.all([ (async () => { let fetchResult = await verifier.fetchAndVerifyIfNeeded(); expect(fetchResult).toBe(2); })(), (async () => { let fetchResult = await verifier.fetchAndVerifyIfNeeded(); expect(fetchResult).toBe(2); })() ]); }); test('trusted deduplication', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); await Promise.all([ (async () => { let fetchResult = await verifier.fetchAndVerifyIfNeeded(); expect(fetchResult).toBe(3); })(), (async () => { let fetchResult = await verifier.fetchAndVerifyIfNeeded(); expect(fetchResult).toBe(3); })() ]); // Doesn't really test it but it gets the code coverage B) }); test('trusted deduplication with unverification', async () => { const { primaryPromise, trustedPromise, verifyPromise, verifier } = AutoVerifierTest.createPromises(null, 3, 35, 65, 15); await Promise.all([ (async () => { let fetchPromise = verifier.fetchAndVerifyIfNeeded(); let fetchResult = await fetchPromise; expect(fetchResult).toBe(null); })(), (async () => { let fetchPromise = verifier.fetchAndVerifyIfNeeded(); await trustedPromise; verifier.unverify(); let fetchResult = await fetchPromise; expect(fetchResult).toBe(null); })() ]); }); }); // TODO: changesFunc // TODO: Standard autoverifiers?