diff --git a/packages/simulator/README.md b/packages/simulator/README.md index fcfd3ec..da41f19 100644 --- a/packages/simulator/README.md +++ b/packages/simulator/README.md @@ -6,7 +6,6 @@ allowing you to simulate contract behavior locally without blockchain deployment ## Features - ๐งช **Local Testing** - Test contracts without deployment. -- ๐ญ **Caller Context Simulation** - Test multi-user interactions. - ๐ง **Witness Overrides** - Mock and spy on witness functions. - ๐ **State Inspection** - Access private and contract state. - ๐ **Type-Safe** - Full TypeScript support with generics. @@ -147,50 +146,6 @@ public getBalance(): bigint { ## Advanced Features -### ๐ญ Caller Context Management - -Simulate different users interacting with the contract: - -```typescript -// One-off caller context -simulator.as(alice).transfer(zBob, 100n); -simulator.as(bob).withdraw(50n); - -// Persistent caller context -simulator.setPersistentCaller(alice); -simulator.deposit(100n); // Called by alice -simulator.transfer(zBob, 50n); // Called by alice - -// Reset caller context -simulator.resetCaller(); -``` - -### โ ๏ธ Important: Key Encoding for Contracts - -**Same key, different formats:** - -- `alice` = CoinPublicKey (hex string) - for simulator context. -- `zAlice` = ZswapCoinPublicKey (encoded) - for contract parameters. - -```typescript -// For testing, create mock 64-character hex keys -const alice = '0'.repeat(63) + '1'; // CoinPublicKey format -const bob = '0'.repeat(63) + '2'; - -// Encode for contract use -const zAlice = { bytes: encodeCoinPublicKey(alice) }; // ZswapCoinPublicKey format -const zBob = { bytes: encodeCoinPublicKey(bob) }; - -// Incorrect - using wrong format for `bob` -simulator.as(alice).transfer(bob, 100n); // โ Contracts need encoded format - -// Correct - encoded as ZswapCoinPublicKey -simulator.as(alice).transfer(zBob, 100n); // โ -simulator.as(alice).transferFrom(zAlice, zBob, 100n); // โ -``` - -**Remember**: `as()` takes hex strings, contract circuits take encoded keys. - ### ๐ง Witness Overrides Perfect for testing edge cases and tracking witness usage: @@ -248,25 +203,18 @@ import { encodeCoinPublicKey } from '@midnight-ntwrk/compact-runtime'; import { describe, it, expect, beforeEach } from 'vitest'; import { MyContractSimulator } from './MyContractSimulator'; -describe('MyContract', () => { - let simulator: MyContractSimulator; - let owner = '0'.repeat(63) + '1'; - let zOwner = { bytes: encodeCoinPublicKey(owner) }; +let simulator: MyContractSimulator; +let val = 123n; +let newVal = 456n; +describe('MyContract', () => { beforeEach(() => { - simulator = new MyContractSimulator( - zOwner, - { privateState: MyPrivateState.generate() } - ); + simulator = new MyContractSimulator(val); }); - it('should transfer ownership', () => { - let newOwner = '0'.repeat(63) + '2'; - let zNewOwner = { bytes: encodeCoinPublicKey(newOwner) }; - - simulator.as(owner).transferOwnership(zNewOwner); - - expect(simulator.getPublicState()._owner).toEqual(zNewOwner); + it('should set new value', () => { + simulator.setVal(newVal); + expect(simulator.getPublicState()._val).toEqual(newVal); }); }); ``` @@ -289,36 +237,6 @@ it('should handle custom witness behavior', () => { }); ``` -### Multi-User Interactions - -```typescript -it('should handle multi-user token transfers', () => { - // PKs - const alice = '0'.repeat(63) + '1'; - const zAlice = { bytes: encodeCoinPublicKey(alice) }; - - const bob = '0'.repeat(63) + '2'; - const zBob = { bytes: encodeCoinPublicKey(bob) }; - - const charlie = '0'.repeat(63) + '3'; - const zCharlie = { bytes: encodeCoinPublicKey(charlie) }; - - // Alice deposits - simulator.as(alice).deposit(1000n); - - // Alice transfers to Bob - simulator.as(alice).transfer(zBob, 300n); - - // Bob transfers to Charlie - simulator.as(bob).transfer(zCharlie, 100n); - - const state = simulator.getPublicState(); - expect(state._balances.lookup(zAlice)).toBe(700n); - expect(state._balances.lookup(zBob)).toBe(200n); - expect(state._balances.lookup(zCharlie)).toBe(100n); -}); -``` - ## Special Cases ### Contracts with No Constructor Arguments @@ -363,10 +281,7 @@ interface BaseSimulatorOptions
{ ### Core Methods | Method | Description | -|--------|-------------| -| `as(caller)` | Execute next operation as specified caller | -| `setPersistentCaller(caller)` | Set persistent caller for all operations | -| `resetCaller()` | Clears the caller context | +| ------ | ----------- | | `overrideWitness(key, fn)` | Override a specific witness function | | `getPrivateState()` | Get current private state | | `getPublicState()` | Get current public ledger state | @@ -378,4 +293,3 @@ interface BaseSimulatorOptions
{ 2. **Witness Testing**: Use witness overrides to test edge cases without modifying contract code. 3. **Deterministic Tests**: Override witnesses with fixed values for reproducible tests. 4. **State Validation**: Inspect state after operations to ensure correctness. -5. **Multi-User Testing**: Use caller context to simulate realistic multi-user scenarios. diff --git a/packages/simulator/src/core/AbstractSimulator.ts b/packages/simulator/src/core/AbstractSimulator.ts index 52be1b5..b8af16d 100644 --- a/packages/simulator/src/core/AbstractSimulator.ts +++ b/packages/simulator/src/core/AbstractSimulator.ts @@ -41,7 +41,13 @@ export abstract class AbstractSimulator
/** * Sets the caller context for the next circuit call only (auto-resets). * - * @param caller - The public key to use as the caller for the next circuit execution + * @notice ownPublicKey() is a witness value and MUST NOT be used + * as an authentication mechanism. Any contract using ownPublicKey() + * directly for access control is insecure. This method exists to + * support circuits that use ownPublicKey() as an input to other + * computations (e.g., commitment derivation). + * + * @param caller - The coin public key to use as the caller for the next circuit execution * @returns This simulator instance for method chaining */ public as(caller: CoinPublicKey): this { @@ -52,6 +58,12 @@ export abstract class AbstractSimulator
/** * Sets a persistent caller that will be used for all subsequent circuit calls. * + * @notice ownPublicKey() is a witness value and MUST NOT be used + * as an authentication mechanism. Any contract using ownPublicKey() + * directly for access control is insecure. This method exists to + * support circuits that use ownPublicKey() as an input to other + * computations (e.g., commitment derivation). + * * @param caller - The public key to use as the caller for all future calls, or null to clear */ public setPersistentCaller(caller: CoinPublicKey | null): void { @@ -61,6 +73,12 @@ export abstract class AbstractSimulator
/** * Clears persistent caller overrides. * + * @notice ownPublicKey() is a witness value and MUST NOT be used + * as an authentication mechanism. Any contract using ownPublicKey() + * directly for access control is insecure. This method exists to + * support circuits that use ownPublicKey() as an input to other + * computations (e.g., commitment derivation). + * * @returns This simulator instance for method chaining */ public resetCaller(): this { diff --git a/packages/simulator/src/core/ContractSimulator.ts b/packages/simulator/src/core/ContractSimulator.ts index 9321626..2b114a3 100644 --- a/packages/simulator/src/core/ContractSimulator.ts +++ b/packages/simulator/src/core/ContractSimulator.ts @@ -26,6 +26,12 @@ export abstract class ContractSimulator
extends AbstractSimulator
{ /** * Constructs a circuit context with appropriate caller information. * + * @notice ownPublicKey() is a witness value and MUST NOT be used + * as an authentication mechanism. Any contract using ownPublicKey() + * directly for access control is insecure. This method exists to + * support circuits that use ownPublicKey() as an input to other + * computations (e.g., commitment derivation). + * * Checks for caller overrides in priority order: * 1. Single-use override (set via `as(caller)`) * 2. Persistent override (set via `setPersistentCaller(caller)`)