@nori-zk/mina-token-bridge
Version:
A Mina zk-program contract allowing users to mint tokens on Nori Bridge.
110 lines (102 loc) • 5.52 kB
JavaScript
import { LocalStorageSim } from './localStorageSim.js';
import { createActor, waitFor } from 'xstate';
import { getBridgeStateTopic$, getBridgeTimingsTopic$, getEthStateTopic$, } from '../rx/topics.js';
import { firstValueFrom, map, shareReplay, take } from 'rxjs';
import { getReconnectingBridgeSocket$ } from '../rx/socket.js';
describe('depositMachine', () => {
let depositMachine;
const { bridgeSocket$ } = getReconnectingBridgeSocket$();
// Seem to need to add share replay to avoid contention.
const ethStateTopic$ = getEthStateTopic$(bridgeSocket$).pipe(shareReplay(1));
const bridgeStateTopic$ = getBridgeStateTopic$(bridgeSocket$).pipe(shareReplay(1));
const bridgeTimingsTopic$ = getBridgeTimingsTopic$(bridgeSocket$).pipe(shareReplay(1));
// Turn the topics into hot observables... (this is slightly annoying to have to do)
ethStateTopic$.subscribe();
bridgeStateTopic$.subscribe();
bridgeTimingsTopic$.subscribe();
beforeEach(async () => {
// Fresh window + localStorage before each test
global.window = {
localStorage: new LocalStorageSim(),
};
// Import machine which relies on the window.localStorage sim.
const { getDepositMachine } = await import('./deposit.js');
// Construct the machine
depositMachine = getDepositMachine({
ethStateTopic$: ethStateTopic$,
bridgeStateTopic$: bridgeStateTopic$,
bridgeTimingsTopic$: bridgeTimingsTopic$,
});
});
// Utility to get a deposit block number
async function getNextDepositTarget() {
const nextFinalizationTarget$ = ethStateTopic$.pipe(take(1));
return firstValueFrom(nextFinalizationTarget$.pipe(map(({ latest_finality_block_number }) => latest_finality_block_number + 10)));
}
test('should be in noActiveDepositNumber when no keys exist', () => {
window.localStorage.clear();
const actor = createActor(depositMachine).start();
expect(actor.getSnapshot().value).toBe('noActiveDepositNumber');
});
test('should transition to hasActiveDepositNumber with only deposit number', async () => {
const depositBlockNumber = await getNextDepositTarget();
window.localStorage.clear();
window.localStorage.setItem('activeDepositNumber', depositBlockNumber.toString());
const actor = createActor(depositMachine);
actor.start();
await waitFor(actor, (state) => state.matches('hasActiveDepositNumber'));
expect(actor.getSnapshot().value).toBe('hasActiveDepositNumber');
});
test('should prioritize computedEthProof over other states', async () => {
window.localStorage.clear();
window.localStorage.setItem('activeDepositNumber', '1233333333333333');
window.localStorage.setItem('computedEthProof', 'proof-123');
window.localStorage.setItem('depositMintTx', 'tx-123');
const actor = createActor(depositMachine).start();
await waitFor(actor, (state) => state.matches('hasComputedEthProof'));
expect(actor.getSnapshot().value).toBe('hasComputedEthProof');
});
test('should handle missed opportunity immediately', async () => {
window.localStorage.clear();
// Use an old deposit number to trigger missed opportunity naturally
const latestBlock = await getNextDepositTarget();
const depositBlockNumber = latestBlock - 10000;
const actor = createActor(depositMachine).start();
actor.send({ type: 'SET_DEPOSIT_NUMBER', value: depositBlockNumber });
await new Promise((resolve) => {
actor.subscribe({
complete: () => {
resolve();
},
});
});
expect(actor.getSnapshot().value).toBe('missedOpportunity');
});
// This test is WIP
/*test('should complete full workflow without delays', async () => {
const depositBlockNumber = await getNextDepositTarget();
const actor = createActor(depositMachine).start();
actor.subscribe((snapshot)=> console.log(snapshot.context, snapshot.value));
actor.send({ type: 'SET_DEPOSIT_NUMBER', value: depositBlockNumber });
await waitFor(actor, (state) =>
state.matches('hasActiveDepositNumber')
);
//await waitFor(actor, (state) => state.matches('checkingCanCompute'));
// Wait for machine to detect CanCompute naturally and transition
await waitFor(actor, (state) => state.matches('computingEthProof'));
// Wait for proof computation and transition
await waitFor(actor, (state) => state.matches('hasComputedEthProof'));
// Wait for canMintEvaluation state naturally
await waitFor(actor, (state) => state.matches('canMintEvaluation'));
// Wait for machine to transition to buildingMintTx
await waitFor(actor, (state) => state.matches('buildingMintTx'));
// Wait for mint tx to be built and move to hasDepositMintTx
await waitFor(actor, (state) => state.matches('hasDepositMintTx'));
// Submit mint transaction event and wait for submission
actor.send({ type: 'SUBMIT_MINT_TX' });
await waitFor(actor, (state) => state.matches('submittingMintTx'));
// Wait for submission completion (done.invoke event must be triggered internally)
await waitFor(actor, (state) => state.matches('completed'));
});*/
});
//# sourceMappingURL=deposit.spec.js.map