From 2268788cf5c4c4f34722902bf48dfc62c0f58de3 Mon Sep 17 00:00:00 2001 From: aii23 Date: Sat, 5 Oct 2024 18:04:38 +0700 Subject: [PATCH 1/6] Partially done. RandomManager is deployed via factory --- scripts/utils.ts | 31 +- src/Factory.ts | 139 ++ src/PLottery.ts | 1171 +++++++---------- src/Proofs/TicketReduceProof.ts | 91 +- src/Random/RandomManager.ts | 325 ++--- src/StateManager/BaseStateManager.ts | 203 +-- src/StateManager/FactoryStateManager.ts | 38 + src/StateManager/PStateManager.ts | 99 +- src/StateManager/RandomManagerManager.ts | 91 +- src/Tests/MockedContracts/MockedFactory.ts | 137 ++ .../MockedContracts/MockedRandomManager.ts | 8 + src/Tests/Random.test.ts | 123 +- src/util.ts | 32 - 13 files changed, 1091 insertions(+), 1397 deletions(-) create mode 100644 src/Factory.ts create mode 100644 src/StateManager/FactoryStateManager.ts create mode 100644 src/Tests/MockedContracts/MockedFactory.ts create mode 100644 src/Tests/MockedContracts/MockedRandomManager.ts diff --git a/scripts/utils.ts b/scripts/utils.ts index f03510c..8ebf3b5 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -2,8 +2,6 @@ import dotenv from 'dotenv'; dotenv.config(); import { Cache, Field, Mina, PrivateKey, PublicKey } from 'o1js'; import * as fs from 'fs'; -import { PLotteryType, getPLottery } from '../src/PLottery.js'; -import { getRandomManager } from '../src/Random/RandomManager.js'; import { LotteryAction, TicketReduceProgram, @@ -33,7 +31,7 @@ export const configDefaultInstance = (): { transactionFee: number } => { return { transactionFee }; }; - +/* export const findPlottery = (epoch: string = 'current') => { let addressesBuffer = fs.readFileSync(`./deploy/addresses/${epoch}.json`); let addresses: { @@ -150,6 +148,20 @@ export const compilePlottery = async (epoch: string = 'current') => { }); }; +export const getDeployer = (): { + deployer: PublicKey; + deployerKey: PrivateKey; +} => { + let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); + let deployer = deployerKey.toPublicKey(); + + return { + deployer, + deployerKey, + }; +}; +*/ + export const getIPFSCID = (): { hashPart1: Field; hashPart2: Field } => { function segmentHash(ipfsHashFile: string) { const ipfsHash0 = ipfsHashFile.slice(0, 30); // first part of the ipfsHash @@ -166,16 +178,3 @@ export const getIPFSCID = (): { hashPart1: Field; hashPart2: Field } => { return segmentHash(cidBuffer.toString()); }; - -export const getDeployer = (): { - deployer: PublicKey; - deployerKey: PrivateKey; -} => { - let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); - let deployer = deployerKey.toPublicKey(); - - return { - deployer, - deployerKey, - }; -}; diff --git a/src/Factory.ts b/src/Factory.ts new file mode 100644 index 0000000..09f3c36 --- /dev/null +++ b/src/Factory.ts @@ -0,0 +1,139 @@ +import { + AccountUpdate, + Bool, + Field, + MerkleMap, + MerkleMapWitness, + Poseidon, + PublicKey, + SmartContract, + State, + state, + Struct, + UInt64, + Permissions, + method, + Cache, +} from 'o1js'; +import { BLOCK_PER_ROUND } from './constants'; +import { MerkleMap20 } from './Structs/CustomMerkleMap'; +import { RandomManager } from './Random/RandomManager'; +import { PLottery } from './PLottery'; +import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; +import { TicketReduceProgram } from './Proofs/TicketReduceProof'; +import { DistributionProgram } from './Proofs/DistributionProof'; + +const emptyMerkleMapRoot = new MerkleMap().getRoot(); + +await ZkonZkProgram.compile({ cache: Cache.FileSystem('cache') }); +await ZkonRequestCoordinator.compile({ cache: Cache.FileSystem('cache') }); +const { verificationKey: randomManagerVK } = await RandomManager.compile({ + cache: Cache.FileSystem('cache'), +}); +// const { verificationKey: mockedRandomManagerVK } = +// await MockedRandomManager.compile(); +await TicketReduceProgram.compile({ cache: Cache.FileSystem('cache') }); +await DistributionProgram.compile({ cache: Cache.FileSystem('cache') }); +const { verificationKey: PLotteryVK } = await PLottery.compile({ + cache: Cache.FileSystem('cache'), +}); + +class RoundInfo extends Struct({ + startSlot: Field, + randomManagerAddress: PublicKey, +}) {} + +export class DeployEvent extends Struct({ + round: Field, + randomManager: PublicKey, + plottery: PublicKey, +}) {} + +const startSlot = Field(0); + +export class PlotteryFactory extends SmartContract { + events = { + 'deploy-plottery': DeployEvent, + }; + + @state(Field) roundsRoot = State(); + + init() { + super.init(); + this.roundsRoot.set(emptyMerkleMapRoot); + } + + @method + async deployRound( + witness: MerkleMapWitness, + randomManager: PublicKey, + plottery: PublicKey + ) { + // Check if round was not used and update merkle map + const curRoot = this.roundsRoot.getAndRequireEquals(); + const [expectedRoot, round] = witness.computeRootAndKeyV2(Field(0)); + curRoot.assertEquals(expectedRoot, 'Wrong witness'); + + const [newRoot] = witness.computeRootAndKeyV2(Field(1)); + this.roundsRoot.set(newRoot); + + // Deploy and initialize random manager + { + const rmUpdate = AccountUpdate.createSigned(randomManager); + rmUpdate.account.verificationKey.set(randomManagerVK); + rmUpdate.update.appState[0] = { + isSome: Bool(true), + value: startSlot, + }; + + // Update permissions + rmUpdate.body.update.permissions = { + isSome: Bool(true), + value: { + ...Permissions.default(), + }, + }; + } + // Deploy plottery + { + const plotteryUpdate = AccountUpdate.createSigned(plottery); + plotteryUpdate.account.verificationKey.set(PLotteryVK); + // Start slot set + plotteryUpdate.update.appState[0] = { + isSome: Bool(true), + value: startSlot, + }; + // Set random manager + plotteryUpdate.update.appState[1] = { + isSome: Bool(true), + value: randomManager.x, // ? + }; + + // Set ticket ticketRoot + plotteryUpdate.update.appState[2] = { + isSome: Bool(true), + value: new MerkleMap20().getRoot(), + }; + + // Set ticket nullifier + plotteryUpdate.update.appState[3] = { + isSome: Bool(true), + value: new MerkleMap20().getRoot(), + }; + + // Update permissions + plotteryUpdate.body.update.permissions = { + isSome: Bool(true), + value: { + ...Permissions.default(), + }, + }; + } + + // Emit event + this.emitEvent( + 'deploy-plottery', + new DeployEvent({ round, plottery, randomManager }) + ); + } +} diff --git a/src/PLottery.ts b/src/PLottery.ts index faa6973..eea0576 100644 --- a/src/PLottery.ts +++ b/src/PLottery.ts @@ -17,6 +17,7 @@ import { Permissions, TransactionVersion, VerificationKey, + Bool, } from 'o1js'; import { Ticket } from './Structs/Ticket.js'; import { @@ -30,20 +31,14 @@ import { treasury, } from './constants.js'; import { DistributionProof } from './Proofs/DistributionProof.js'; -import { - NumberPacked, - convertToUInt32, - convertToUInt64, - getEmpty2dMerkleMap, - getNullifierId, -} from './util.js'; +import { NumberPacked, convertToUInt32, convertToUInt64 } from './util.js'; import { MerkleMap20, MerkleMap20Witness } from './Structs/CustomMerkleMap.js'; import { ActionList, LotteryAction, TicketReduceProof, } from './Proofs/TicketReduceProof.js'; -import { getRandomManager } from './Random/RandomManager.js'; +import { RandomManager } from './Random/RandomManager.js'; export interface MerkleCheckResult { key: Field; @@ -73,9 +68,6 @@ const max = (a: UInt64, b: UInt64): UInt64 => { const emptyMapRoot = new MerkleMap().getRoot(); const emptyMap20Root = new MerkleMap20().getRoot(); -const empty2dMap = getEmpty2dMerkleMap(20); -const empty2dMapRoot = empty2dMap.getRoot(); - export class BuyTicketEvent extends Struct({ ticket: Ticket, round: Field, @@ -96,689 +88,484 @@ export class RefundEvent extends Struct({ round: Field, }) {} -export class ReduceEvent extends Struct({ - startActionState: Field, - endActionState: Field, -}) {} +export class ReduceEvent extends Struct({}) {} -export function getPLottery( - randomManagerAddress: PublicKey, - randomManagerOwner: PublicKey, - coordinatorAddress: PublicKey = ZkOnCoordinatorAddress -) { - class RandomManager extends getRandomManager( - randomManagerOwner, - coordinatorAddress - ) {} - - class PLottery extends SmartContract { - reducer = Reducer({ actionType: LotteryAction }); - - events = { - 'buy-ticket': BuyTicketEvent, - 'produce-result': ProduceResultEvent, - 'get-reward': GetRewardEvent, - 'get-refund': RefundEvent, - reduce: ReduceEvent, - }; - // Stores merkle map with all tickets, that user have bought. Each leaf of this tree is a root of tree for corresponding round - @state(Field) ticketRoot = State(); - - // Stores nullifier tree root for tickets, so one ticket can't be used twice - @state(Field) ticketNullifier = State(); - - // Stores merkle map with total bank for each round. - @state(Field) bankRoot = State(); - - // Stores merkle map with wining combination for each rounds - @state(Field) roundResultRoot = State(); - - // Stores block of deploy - @state(UInt32) startBlock = State(); - - // Stores last action state, that was processed by reducer - @state(Field) lastProcessedState = State(); - - // Round in witch last reduce happened - @state(Field) lastReduceInRound = State(); - - // Last processed ticketId by reducer - @state(Field) lastProcessedTicketId = State(); - - init() { - super.init(); - - this.ticketRoot.set(empty2dMapRoot); - this.ticketNullifier.set(emptyMapRoot); - this.bankRoot.set(emptyMap20Root); - this.roundResultRoot.set(emptyMap20Root); - this.startBlock.set( - this.network.globalSlotSinceGenesis.getAndRequireEquals() - ); - this.lastProcessedState.set(Reducer.initialActionState); - this.lastProcessedTicketId.set(Field(-1)); - - this.account.permissions.set({ - ...Permissions.default(), - setVerificationKey: - Permissions.VerificationKey.impossibleDuringCurrentVersion(), - }); - } - - /** - * @notice Set verification key for account - * @dev verification key can be updated only if Mina hardfork happen. It allows zkApp to be live after Mina hardfork - * @param vk Verification key - */ - @method async updateVerificationKey(vk: VerificationKey) { - this.account.verificationKey.set(vk); - } - - /** - * @notice Allows a user to buy a lottery ticket for a specific round. - * @dev No ticket merkle tree update happens here. Only action is dispatched. - * - * @param ticket The lottery ticket being purchased. - * @param round The lottery round for which the ticket is being purchased. - * - * @require The sender must be the owner of the ticket. - * @require The ticket must be valid as per the ticket validity check. - * @require The specified round must be the current lottery round. - * - * @event buy-ticket Emitted when a ticket is successfully purchased. - */ - @method async buyTicket(ticket: Ticket, round: Field) { - // Ticket validity check - ticket.owner.assertEquals(this.sender.getAndRequireSignature()); - ticket.check().assertTrue(); - - // Round check - this.checkCurrentRound(convertToUInt32(round)); - - // Take ticket price from user - let senderUpdate = AccountUpdate.createSigned( - this.sender.getAndRequireSignature() - ); - - const emptyPrice = TICKET_PRICE.mul(COMMISSION).div(PRECISION); - const realPrice = TICKET_PRICE.mul(ticket.amount); - const price = max(emptyPrice, realPrice); - - senderUpdate.send({ to: this, amount: price }); - - // Dispatch action and emit event - this.reducer.dispatch( - new LotteryAction({ - ticket, - round, - }) - ); - this.emitEvent( - 'buy-ticket', - new BuyTicketEvent({ - ticket, - round: round, - }) - ); - } - - /** - * @notice Reduce tickets that lies as actions. - * @dev This function verifies the proof and ensures that the contract's state matches the state described in the proof. - * It then updates the tickets merkle tree, populating it with new tickets. - * - * @param reduceProof The proof that validates the ticket reduction process and contains the new contract state. - * - * @require The proof must be valid and successfully verified. - * @require The processed action list in the proof must be empty, indicating that all actions have been processed. - * @require The contract's last processed state must match the initial state in the proof. - * @require The contract's action state must match the final state in the proof. - * @require The contract's last processed ticket ID must match the initial ticket ID in the proof. - * - * @event reduce Emitted when the tickets are successfully reduced and the contract state is updated. - */ - @method async reduceTickets(reduceProof: TicketReduceProof) { - // Check proof validity - reduceProof.verify(); - - let lastProcessedState = this.lastProcessedState.getAndRequireEquals(); - let lastProcessedTicketId = - this.lastProcessedTicketId.getAndRequireEquals(); - let initialRoot = this.ticketRoot.getAndRequireEquals(); - let initialBankRoot = this.bankRoot.getAndRequireEquals(); - - // Check match of proof data and onchain values. - - // Check initial root equal - reduceProof.publicOutput.initialTicketRoot.assertEquals( - initialRoot, - 'Wrong initial root' - ); - - reduceProof.publicOutput.initialBankRoot.assertEquals( - initialBankRoot, - 'Wrong bank root' - ); - - // Check that all actions was processed. - reduceProof.publicOutput.processedActionList.assertEquals( - ActionList.emptyHash, - 'Proof is not complete. Call cutActions first' - ); - - // Check that state on contract is equal to state on proof - lastProcessedState.assertEquals( - reduceProof.publicOutput.initialState, - 'Initial state is not match contract last processed state' - ); - - // Check that actionState is equal to actionState on proof - this.account.actionState.requireEquals( - reduceProof.publicOutput.finalState - ); - - // Check initial ticket id - lastProcessedTicketId.assertEquals( - reduceProof.publicOutput.initialTicketId, - 'Initial ticket id don not match contract last processed ticket id' - ); - - // Update onchain values - this.lastProcessedState.set(reduceProof.publicOutput.finalState); - this.ticketRoot.set(reduceProof.publicOutput.newTicketRoot); - this.bankRoot.set(reduceProof.publicOutput.newBankRoot); - this.lastReduceInRound.set(reduceProof.publicOutput.lastProcessedRound); - this.lastProcessedTicketId.set( - reduceProof.publicOutput.lastProcessedTicketId - ); - - // Emit event - this.emitEvent( - 'reduce', - new ReduceEvent({ - startActionState: reduceProof.publicOutput.initialState, - endActionState: reduceProof.publicOutput.finalState, - }) - ); - } - - /** - * @notice Generate winning combination for round - * @dev Random number seed is taken from RandomManager contract for this round. - * Then using this seed 6 number is generated and stored - * - * @param resultWitness The Merkle proof witness for the current result tree. - * @param bankValue The current value in the bank for this round. - * @param bankWitness The Merkle proof witness for the bank tree. - * @param rmWitness The Merkle proof witness for the random value tree(tree is stored on RandomManager contract). - * @param rmValue The random value used to generate winning numbers. - * - * @require The result for this round must not have been computed yet. - * @require The provided result witness must be valid and match the initial result root. - * @require The round must have been reduced before the result can be computed. - * @require The random value should match one, that is stored on RandomManager contract. - * - * @event produce-result Emitted when the result is successfully produced and the result tree is updated. - */ - @method async produceResult( - resultWitness: MerkleMap20Witness, - bankValue: Field, - bankWitness: MerkleMap20Witness, - rmWitness: MerkleMapWitness, - rmValue: Field - ) { - // Check that result for this round is not computed yet, and that witness is valid - const [initialResultRoot, round] = resultWitness.computeRootAndKeyV2( - Field.from(0) - ); - - this.roundResultRoot - .getAndRequireEquals() - .assertEquals(initialResultRoot, 'Wrong resultWitness or value'); - - this.lastReduceInRound - .getAndRequireEquals() - .assertGreaterThan(round, 'Call reduce for this round first'); - - this.checkRandomResultValue(rmWitness, rmValue, round); - - // Generate new winning combination using random number from NumberManager - let winningNumbers = generateNumbersSeed(rmValue); - let newLeafValue = NumberPacked.pack(winningNumbers); - - // Update result tree - const [newResultRoot] = resultWitness.computeRootAndKeyV2(newLeafValue); - - this.roundResultRoot.set(newResultRoot); - - // Send fee to treasury - this.checkAndUpdateBank( - bankWitness, - round, - bankValue, - bankValue.mul(PRECISION - COMMISSION).div(PRECISION) - ); - - this.send({ - to: treasury, - amount: convertToUInt64(bankValue.mul(COMMISSION).div(PRECISION)), - }); - - this.emitEvent( - 'produce-result', - new ProduceResultEvent({ - result: newLeafValue, - round, - }) - ); - } - - // Update refund natspec - /** - * @notice Processes a refund for a lottery ticket if the result for the round was not generated within 2 days. - * @dev This function ensures that the ticket owner is the one requesting the refund, verifies the ticket's validity - * in the Merkle maps, checks that the result for the round is zero, and processes the refund after verifying - * and updating the necessary states. - * - * @param ticket The lottery ticket for which the refund is being requested. - * @param roundWitness The 1st level Merkle proof witness for the tickets tree. - * @param roundTicketWitness The 2nd level Merkle proof witness for the round's ticket tree. - * @param resultWitness The Merkle proof witness for the result tree. - * @param bankValue The value of bank for that round. - * @param bankWitness The Merkle proof witness for bank tree. - * - * @require The sender must be the owner of the ticket. - * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. - * @require The result for the round must be zero to be eligible for a refund. - * @require The refund can only be processed after approximately two days since the round finished. - * - * @event get-refund Emitted when a refund is successfully processed and the ticket price is returned to the user. - */ - @method async refund( - ticket: Ticket, - roundWitness: MerkleMap20Witness, - roundTicketWitness: MerkleMap20Witness, - resultWitness: MerkleMap20Witness, - bankValue: Field, - bankWitness: MerkleMap20Witness - ) { - // Check that owner trying to claim - ticket.owner.assertEquals(this.sender.getAndRequireSignature()); - - // Check ticket in merkle map and set ticket to zero - const { round } = this.checkAndUpdateTicket( - roundWitness, - roundTicketWitness, - ticket.hash(), - Field(0) - ); - - // Check that result is zero for this round - this.checkResult(resultWitness, round, Field(0)); - - // Can call refund after ~ 2 days after round finished - this.checkRoundPass(convertToUInt32(round.add(2))); - - // Check and update bank witness - const totalTicketPrice = ticket.amount.mul(TICKET_PRICE); - const newBankValue = bankValue.sub(totalTicketPrice.value); - this.checkAndUpdateBank(bankWitness, round, bankValue, newBankValue); - - // Send ticket price back to user - this.send({ - to: ticket.owner, - amount: totalTicketPrice, - }); - - this.emitEvent( - 'get-refund', - new RefundEvent({ - ticket, - round, - }) - ); - } - - /** - * @notice Claims the reward for a winning lottery ticket. - * @dev This function calculate ticket score, totalScore is obtained from DistributionProof, - * and then sends appropriate potion of bank to ticket owner. Finally it nullify the ticket. - * - * @param ticket The lottery ticket for which the reward is being claimed. - * @param roundWitness The 1s level Merkle proof witness for the ticket tree. - * @param roundTicketWitness The 2nd level Merkle proof witness for the ticket tree. - * @param dp The distribution proof to verify the winning numbers and ticket distribution. - * @param winningNumbers The winning numbers for the current round. - * @param resultWitness The Merkle proof witness for the result tree. - * @param bankValue The current value in the bank for this round. - * @param bankWitness The Merkle proof witness for the bank tree. - * @param nullifierWitness The Merkle proof witness for the nullifier tree. - * - * @require The sender must be the owner of the ticket. - * @require The distribution proof must be valid and match the round's ticket root and winning numbers. - * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. - * @require The actions for the round must be reduced before claiming the reward. - * @require The result root must match the winning numbers for the round. - * @require The bank value must be verified and sufficient to cover the reward. - * - * @event get-reward Emitted when the reward is successfully claimed and transferred to the ticket owner. - */ - @method async getReward( - ticket: Ticket, - roundWitness: MerkleMap20Witness, - roundTicketWitness: MerkleMap20Witness, - dp: DistributionProof, - winningNumbers: Field, - resultWitness: MerkleMap20Witness, - bankValue: Field, - bankWitness: MerkleMap20Witness, - nullifierWitness: MerkleMapWitness - ) { - // Check that owner trying to claim - ticket.owner.assertEquals(this.sender.getAndRequireSignature()); - // Verify distribution proof - dp.verify(); - - // Check ticket in tree - const { - ticketId, - roundRoot: roundTicketRoot, - round, - } = this.checkTicket(roundWitness, roundTicketWitness, ticket.hash()); - - dp.publicOutput.root.assertEquals( - roundTicketRoot, - 'Wrong distribution proof' - ); - - dp.publicInput.winningCombination.assertEquals( - winningNumbers, - 'Wrong winning numbers in dp' - ); - - round.assertLessThan( - this.lastReduceInRound.getAndRequireEquals(), - 'Actions was not reduced for this round yet. Call reduceTickets first' - ); - - // Check result root info - this.checkResult(resultWitness, round, winningNumbers); - - // Compute score using winning ticket - const score = ticket.getScore(NumberPacked.unpack(winningNumbers)); - const totalScore = dp.publicOutput.total; - - const payAmount = convertToUInt64(bankValue).mul(score).div(totalScore); - // Pay user - this.checkBank(bankWitness, round, bankValue); - - this.send({ - to: ticket.owner, - amount: payAmount, - }); - - // Add ticket to nullifier - this.checkAndUpdateNullifier( - nullifierWitness, - getNullifierId(round, ticketId), - Field(0), - Field.from(1) - ); - - this.emitEvent( - 'get-reward', - new GetRewardEvent({ - ticket, - round, - }) - ); - } - - /** - * @notice Checks the validity of the random result value for a specific round. - * @dev This function verifies that the random result value is greater than zero, confirms that the result root - * from the Random Manager matches the provided witness, and ensures the round matches the expected round. - * - * @param roundResultWitness The Merkle proof witness for the round result value. - * @param roundResultValue The random result value to be checked. - * @param round The round for which the random result value is being checked. - * - * @require The random result value must be greater than zero. - * @require The result root from the Random Manager must match the provided witness. - * @require The round number must match the expected round in the proof. - */ - public checkRandomResultValue( - roundResultWitness: MerkleMapWitness, - roundResultValue: Field, - round: Field - ) { - roundResultValue.assertGreaterThan(Field(0)); - const rm = new RandomManager(randomManagerAddress); - const resultRoot = rm.resultRoot.getAndRequireEquals(); - - const [prevResultRoot, prevRound] = - roundResultWitness.computeRootAndKeyV2(roundResultValue); - prevResultRoot.assertEquals( - resultRoot, - 'checkResultValue: wrong result witness' - ); - - prevRound.assertEquals(round, 'checkResultValue: wrong round'); - } - - /** - * @notice Check that execution is happening within provided round - * - * @param round Round to be checked - * - * @require globalSlotSinceGenesis to be within range of round - */ - public checkCurrentRound(round: UInt32) { - const startBlock = this.startBlock.getAndRequireEquals(); - this.network.globalSlotSinceGenesis.requireBetween( - startBlock.add(round.mul(BLOCK_PER_ROUND)), - startBlock.add(round.add(1).mul(BLOCK_PER_ROUND).sub(1)) - ); - } - - /** - * @notice Check that execution is happening after provided round - * - * @param round Round to be checked - * - * @require globalSlotSinceGenesis to be greater then last slot of given number - */ - public checkRoundPass(round: UInt32) { - const startBlock = this.startBlock.getAndRequireEquals(); - this.network.globalSlotSinceGenesis.requireBetween( - startBlock.add(round.add(1).mul(BLOCK_PER_ROUND)), - UInt32.MAXINT() - ); - } - - /** - * @notice Check validity of merkle map witness for result tree. - * - * @param witness Merkle map witness for result tree. - * @param round Optional value for round. If provided - checks, that round match key in . - * @param curValue Value of result to be checked. - * - * @returns key of - */ - public checkResult( - witness: MerkleMap20Witness, - round: Field, - curValue: Field - ): MerkleCheckResult { - return this.checkMap(this.roundResultRoot, witness, round, curValue); - } - - /** - * @notice Check validity of merkle map witness for bank tree. - * - * @param witness Merkle map witness for bank tree. - * @param round Round number, that will be compared with key. - * @param curValue Value of bank to be checked. - * - * @returns key of - */ - public checkBank( - witness: MerkleMap20Witness, - round: Field, - curValue: Field - ): MerkleCheckResult { - return this.checkMap(this.bankRoot, witness, round, curValue); - } - - /** - * @notice Check validity of merkle map witness for bank tree and then updates tree with new value. - * - * @param witness Merkle map witness for bank tree. - * @param round Round number, that will be compared with key. - * @param curValue Value of bank to be checked. - * @param newValue New value that should be store in tree. - * - * @returns key of - */ - public checkAndUpdateBank( - witness: MerkleMap20Witness, - round: Field, - curValue: Field, - newValue: Field - ): MerkleCheckResult { - return this.checkAndUpdateMap( - this.bankRoot, - witness, - round, - curValue, - newValue - ); - } - - /** - * @notice Check validity of merkle map witness for nullifier tree and then updates tree with new value. - * - * @param witness Merkle map witness for nullifier tree. - * @param round Round number, that will be compared with key. - * @param curValue Value of nullifier to be checked. - * @param newValue New value that should be store in tree. - * - * @returns key of - */ - public checkAndUpdateNullifier( - witness: MerkleMapWitness, - key: Field, - curValue: Field, - newValue: Field - ): MerkleCheckResult { - return this.checkAndUpdateMap( - this.ticketNullifier, - witness, - key, - curValue, - newValue - ); - } - - /** - * @notice General method that allows to check and update onchain merkle trees roots - * - * @param state On-chain state, that should be updated. - * @param witness Merkle map witness. - * @param key Key that will be compared with key. - * @param curValue Value to be checked. - * @param newValue New value that should be store in tree. - * - * @returns key of - */ - public checkAndUpdateMap( - state: State, - witness: MerkleMap20Witness | MerkleMapWitness, - key: Field, - curValue: Field, - newValue: Field - ): MerkleCheckResult { - let checkRes = this.checkMap(state, witness, key, curValue); - - const [newRoot] = witness.computeRootAndKeyV2(newValue); - state.set(newRoot); - - return checkRes; - } - - /** - * @notice General method that allows to check onchain merkle trees roots - * - * @param state On-chain state, that should be updated. - * @param witness Merkle map witness. - * @param key Key that will be compared with key. - * @param curValue Value to be checked. - * - * @returns key of - */ - public checkMap( - state: State, - witness: MerkleMap20Witness | MerkleMapWitness, - key: Field, - curValue: Field - ): MerkleCheckResult { - const curRoot = state.getAndRequireEquals(); - - const [prevRoot, witnessKey] = witness.computeRootAndKeyV2(curValue); - curRoot.assertEquals(prevRoot, 'Wrong witness'); - witnessKey.assertEquals(key, 'Wrong key'); - - return { - key: witnessKey, - }; - } - - public checkAndUpdateTicket( - firstWitness: MerkleMap20Witness | MerkleMapWitness, - secondWitness: MerkleMap20Witness | MerkleMapWitness, - prevValue: Field, - newValue: Field - ): { ticketId: Field; round: Field } { - const res = this.checkTicket(firstWitness, secondWitness, prevValue); - - const [newRoot2] = secondWitness.computeRootAndKeyV2(newValue); - const [newRoot1] = firstWitness.computeRootAndKeyV2(newRoot2); - this.ticketRoot.set(newRoot1); - - return res; - } - - /** - * @notice Methods to check if ticket lies on ticket merkle tree. - * @dev We can't use ordinary checkMap, because of two level structure of ticket tree. - * - * @param firstWitness First level witness for ticket tree. - * @param key1 First level key for ticket tree(round). - * @param secondWitness Second level witness for ticket tree. - * @param value Hash of ticket. - * - * @returns key of - */ - public checkTicket( - firstWitness: MerkleMap20Witness | MerkleMapWitness, - secondWitness: MerkleMap20Witness | MerkleMapWitness, - value: Field - ): { ticketId: Field; round: Field; roundRoot: Field } { - const [secondLevelRoot, ticketId] = - secondWitness.computeRootAndKeyV2(value); - - const [firstLevelRoot, round] = - firstWitness.computeRootAndKeyV2(secondLevelRoot); - - this.ticketRoot - .getAndRequireEquals() - .assertEquals(firstLevelRoot, 'Wrong 2d witness'); - - return { ticketId, round, roundRoot: secondLevelRoot }; - } +export class PLottery extends SmartContract { + reducer = Reducer({ actionType: LotteryAction }); + + events = { + 'buy-ticket': BuyTicketEvent, + 'produce-result': ProduceResultEvent, + 'get-reward': GetRewardEvent, + 'get-refund': RefundEvent, + reduce: ReduceEvent, + }; + // !!!!First slot for outer initializer + @state(PublicKey) randomManager = State(); + + // Stores block of deploy + @state(UInt32) startSlot = State(); + + // Stores merkle map with all tickets, that user have bought + @state(Field) ticketRoot = State(); + + // Stores nullifier tree root for tickets, so one ticket can't be used twice + @state(Field) ticketNullifier = State(); + + // Stores merkle map with total bank for each round. + @state(Field) bank = State(); + + // Stores merkle map with wining combination for each rounds + @state(Field) result = State(); + + // Questionable + @state(Bool) reduced = State(); + + // // Round in witch last reduce happened + // @state(Field) lastReduceInRound = State(); + + // // Last processed ticketId by reducer + // @state(Field) lastProcessedTicketId = State(); + + init() { + super.init(); + + /// !!!! This contracts is deployed from factory. No init call there + + this.ticketRoot.set(emptyMap20Root); + this.ticketNullifier.set(emptyMapRoot); + this.startSlot.set( + this.network.globalSlotSinceGenesis.getAndRequireEquals() + ); + + this.account.permissions.set({ + ...Permissions.default(), + setVerificationKey: + Permissions.VerificationKey.impossibleDuringCurrentVersion(), + }); + } + + /** + * @notice Set verification key for account + * @dev verification key can be updated only if Mina hardfork happen. It allows zkApp to be live after Mina hardfork + * @param vk Verification key + */ + @method async updateVerificationKey(vk: VerificationKey) { + this.account.verificationKey.set(vk); + } + + /** + * @notice Allows a user to buy a lottery ticket for a specific round. + * @dev No ticket merkle tree update happens here. Only action is dispatched. + * + * @param ticket The lottery ticket being purchased. + * @param round The lottery round for which the ticket is being purchased. + * + * @require The sender must be the owner of the ticket. + * @require The ticket must be valid as per the ticket validity check. + * @require The specified round must be the current lottery round. + * + * @event buy-ticket Emitted when a ticket is successfully purchased. + */ + @method async buyTicket(ticket: Ticket, round: Field) { + // Ticket validity check + ticket.check().assertTrue(); + + ticket.amount.assertGreaterThan( + UInt64.from(0), + 'Ticket amount should be positive' + ); + + // Round check + this.checkCurrentRound(); + + // Take ticket price from user + let senderUpdate = AccountUpdate.createSigned( + this.sender.getAndRequireSignature() + ); + + senderUpdate.send({ to: this, amount: TICKET_PRICE.mul(ticket.amount) }); + + // Dispatch action and emit event + this.reducer.dispatch( + new LotteryAction({ + ticket, + round, // #TODO remove + }) + ); + this.emitEvent( + 'buy-ticket', + new BuyTicketEvent({ + ticket, + round: round, // #TODO remove + }) + ); + } + + /** + * @notice Reduce tickets that lies as actions. + * @dev This function verifies the proof and ensures that the contract's state matches the state described in the proof. + * It then updates the tickets merkle tree, populating it with new tickets. + * + * @param reduceProof The proof that validates the ticket reduction process and contains the new contract state. + * + * @require The proof must be valid and successfully verified. + * @require The processed action list in the proof must be empty, indicating that all actions have been processed. + * @require The contract's last processed state must match the initial state in the proof. + * @require The contract's action state must match the final state in the proof. + * @require The contract's last processed ticket ID must match the initial ticket ID in the proof. + * + * @event reduce Emitted when the tickets are successfully reduced and the contract state is updated. + */ + @method async reduceTickets(reduceProof: TicketReduceProof) { + // Check proof validity + reduceProof.verify(); + + // Only after round is passed + this.checkRoundPass(UInt32.from(1)); + + let initialRoot = this.ticketRoot.getAndRequireEquals(); + let initialBank = this.bank.getAndRequireEquals(); + + // Check initial root equal + reduceProof.publicOutput.initialTicketRoot.assertEquals( + initialRoot, + 'Wrong initial root' + ); + + reduceProof.publicOutput.initialBank.assertEquals( + initialBank, + 'Wrong bank' + ); + + // Check that all actions was processed. + reduceProof.publicOutput.processedActionList.assertEquals( + ActionList.emptyHash, + 'Proof is not complete. Call cutActions first' + ); + + // Check that actionState is equal to actionState on proof + this.account.actionState + .getAndRequireEquals() + .assertEquals(reduceProof.publicOutput.finalState); + + // Update onchain values + this.ticketRoot.set(reduceProof.publicOutput.newTicketRoot); + this.bank.set(reduceProof.publicOutput.newBank); + this.reduced.set(Bool(true)); + + // Emit event + this.emitEvent('reduce', new ReduceEvent({})); + } + + /** + * @notice Generate winning combination for round + * @dev Random number seed is taken from RandomManager contract for this round. + * Then using this seed 6 number is generated and stored + * + * @param resultWitness The Merkle proof witness for the current result tree. + * @param bankValue The current value in the bank for this round. + * @param bankWitness The Merkle proof witness for the bank tree. + * @param rmWitness The Merkle proof witness for the random value tree(tree is stored on RandomManager contract). + * @param rmValue The random value used to generate winning numbers. + * + * @require The result for this round must not have been computed yet. + * @require The provided result witness must be valid and match the initial result root. + * @require The round must have been reduced before the result can be computed. + * @require The random value should match one, that is stored on RandomManager contract. + * + * @event produce-result Emitted when the result is successfully produced and the result tree is updated. + */ + @method async produceResult() { + // Check that result for this round is not computed yet + const result = this.result.getAndRequireEquals(); + result.assertEquals(Field(0), 'Result for this round is already computed'); + + const reduced = this.reduced.getAndRequireEquals(); + reduced.assertTrue('Actions was not reduced for this round yet'); + + const RM = new RandomManager(this.randomManager.getAndRequireEquals()); + const rmValue = RM.result.getAndRequireEquals(); + + // Generate new winning combination using random number from NumberManager + let winningNumbers = generateNumbersSeed(rmValue); + let resultPacked = NumberPacked.pack(winningNumbers); + + this.result.set(resultPacked); + + const bankValue = this.bank.getAndRequireEquals(); + this.bank.set(bankValue.mul(PRECISION - COMMISSION).div(PRECISION)); + + this.send({ + to: treasury, + amount: convertToUInt64(bankValue.mul(COMMISSION).div(PRECISION)), + }); + + this.emitEvent( + 'produce-result', + new ProduceResultEvent({ + result: resultPacked, + round: Field(0), // #TODO remove + }) + ); + } + + // Update refund natspec + /** + * @notice Processes a refund for a lottery ticket if the result for the round was not generated within 2 days. + * @dev This function ensures that the ticket owner is the one requesting the refund, verifies the ticket's validity + * in the Merkle maps, checks that the result for the round is zero, and processes the refund after verifying + * and updating the necessary states. + * + * @param ticket The lottery ticket for which the refund is being requested. + * @param roundWitness The 1st level Merkle proof witness for the tickets tree. + * @param roundTicketWitness The 2nd level Merkle proof witness for the round's ticket tree. + * @param resultWitness The Merkle proof witness for the result tree. + * @param bankValue The value of bank for that round. + * @param bankWitness The Merkle proof witness for bank tree. + * + * @require The sender must be the owner of the ticket. + * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. + * @require The result for the round must be zero to be eligible for a refund. + * @require The refund can only be processed after approximately two days since the round finished. + * + * @event get-refund Emitted when a refund is successfully processed and the ticket price is returned to the user. + */ + @method async refund(ticket: Ticket, ticketWitness: MerkleMap20Witness) { + // Check that owner trying to claim + ticket.owner.assertEquals(this.sender.getAndRequireSignature()); + + const result = this.result.getAndRequireEquals(); + result.assertEquals(Field(0), 'Result for this round is not zero'); + + // Check ticket in merkle map and set ticket to zero + const [ticketRoot, _] = ticketWitness.computeRootAndKeyV2(ticket.hash()); + this.ticketRoot + .getAndRequireEquals() + .assertEquals(ticketRoot, 'Wrong ticket witness'); + const [newTicketRoot] = ticketWitness.computeRootAndKeyV2(Field(0)); + this.ticketRoot.set(newTicketRoot); + + // Can call refund after ~ 2 days after round finished + this.checkRoundPass(UInt32.from(2)); + + // Check and update bank witness + const totalTicketPrice = ticket.amount.mul(TICKET_PRICE); + const bankValue = this.bank.getAndRequireEquals(); + const newBankValue = bankValue.sub(totalTicketPrice.value); + this.bank.set(newBankValue); + + // Send ticket price back to user + this.send({ + to: ticket.owner, + amount: totalTicketPrice, + }); + + this.emitEvent( + 'get-refund', + new RefundEvent({ + ticket, + round: Field(0), // #TODO remove + }) + ); + } + + /** + * @notice Claims the reward for a winning lottery ticket. + * @dev This function calculate ticket score, totalScore is obtained from DistributionProof, + * and then sends appropriate potion of bank to ticket owner. Finally it nullify the ticket. + * + * @param ticket The lottery ticket for which the reward is being claimed. + * @param roundWitness The 1s level Merkle proof witness for the ticket tree. + * @param roundTicketWitness The 2nd level Merkle proof witness for the ticket tree. + * @param dp The distribution proof to verify the winning numbers and ticket distribution. + * @param winningNumbers The winning numbers for the current round. + * @param resultWitness The Merkle proof witness for the result tree. + * @param bankValue The current value in the bank for this round. + * @param bankWitness The Merkle proof witness for the bank tree. + * @param nullifierWitness The Merkle proof witness for the nullifier tree. + * + * @require The sender must be the owner of the ticket. + * @require The distribution proof must be valid and match the round's ticket root and winning numbers. + * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. + * @require The actions for the round must be reduced before claiming the reward. + * @require The result root must match the winning numbers for the round. + * @require The bank value must be verified and sufficient to cover the reward. + * + * @event get-reward Emitted when the reward is successfully claimed and transferred to the ticket owner. + */ + @method async getReward( + ticket: Ticket, + ticketWitness: MerkleMap20Witness, + dp: DistributionProof, + nullifierWitness: MerkleMapWitness + ) { + // Check that owner trying to claim + ticket.owner.assertEquals(this.sender.getAndRequireSignature()); + // Verify distribution proof + dp.verify(); + + // Check ticket in tree + const [ticketRoot, ticketId] = ticketWitness.computeRootAndKeyV2( + ticket.hash() + ); + this.ticketRoot + .getAndRequireEquals() + .assertEquals(ticketRoot, 'Wrong ticket witness'); + + dp.publicOutput.root.assertEquals(ticketRoot, 'Wrong distribution proof'); + + const winningNumbers = this.result.getAndRequireEquals(); + + dp.publicInput.winningCombination.assertEquals( + winningNumbers, + 'Wrong winning numbers in dp' + ); + + this.reduced + .getAndRequireEquals() + .assertTrue('Actions was not reduced yet'); + + // Compute score using winning ticket + const score = ticket.getScore(NumberPacked.unpack(winningNumbers)); + const totalScore = dp.publicOutput.total; + + const bank = this.bank.getAndRequireEquals(); + + const payAmount = convertToUInt64(bank).mul(score).div(totalScore); + + this.send({ + to: ticket.owner, + amount: payAmount, + }); + + // Add ticket to nullifier + this.checkAndUpdateNullifier( + nullifierWitness, + ticketId, + Field(0), + Field.from(1) + ); + + this.emitEvent( + 'get-reward', + new GetRewardEvent({ + ticket, + round: Field(0), // #TODO remove + }) + ); + } + + /** + * @notice Check that execution is happening within provided round + * + * @param round Round to be checked + * + * @require globalSlotSinceGenesis to be within range of round + */ + public checkCurrentRound() { + const startSlot = this.startSlot.getAndRequireEquals(); + this.network.globalSlotSinceGenesis.requireBetween( + startSlot, + startSlot.add(BLOCK_PER_ROUND).sub(1) + ); + } + + /** + * @notice Check that execution is happening after provided round + * + * @param round Round to be checked + * + * @require globalSlotSinceGenesis to be greater then last slot of given number + */ + public checkRoundPass(amount: UInt32) { + const startSlot = this.startSlot.getAndRequireEquals(); + this.network.globalSlotSinceGenesis.requireBetween( + startSlot.add(amount.mul(BLOCK_PER_ROUND)), + UInt32.MAXINT() + ); + } + + /** + * @notice Check validity of merkle map witness for nullifier tree and then updates tree with new value. + * + * @param witness Merkle map witness for nullifier tree. + * @param round Round number, that will be compared with key. + * @param curValue Value of nullifier to be checked. + * @param newValue New value that should be store in tree. + * + * @returns key of + */ + public checkAndUpdateNullifier( + witness: MerkleMapWitness, + key: Field, + curValue: Field, + newValue: Field + ): MerkleCheckResult { + return this.checkAndUpdateMap( + this.ticketNullifier, + witness, + key, + curValue, + newValue + ); } - return PLottery; + /** + * @notice General method that allows to check and update onchain merkle trees roots + * + * @param state On-chain state, that should be updated. + * @param witness Merkle map witness. + * @param key Key that will be compared with key. + * @param curValue Value to be checked. + * @param newValue New value that should be store in tree. + * + * @returns key of + */ + public checkAndUpdateMap( + state: State, + witness: MerkleMap20Witness | MerkleMapWitness, + key: Field, + curValue: Field, + newValue: Field + ): MerkleCheckResult { + let checkRes = this.checkMap(state, witness, key, curValue); + + const [newRoot] = witness.computeRootAndKeyV2(newValue); + state.set(newRoot); + + return checkRes; + } + + /** + * @notice General method that allows to check onchain merkle trees roots + * + * @param state On-chain state, that should be updated. + * @param witness Merkle map witness. + * @param key Key that will be compared with key. + * @param curValue Value to be checked. + * + * @returns key of + */ + public checkMap( + state: State, + witness: MerkleMap20Witness | MerkleMapWitness, + key: Field, + curValue: Field + ): MerkleCheckResult { + const curRoot = state.getAndRequireEquals(); + + const [prevRoot, witnessKey] = witness.computeRootAndKeyV2(curValue); + curRoot.assertEquals(prevRoot, 'Wrong witness'); + witnessKey.assertEquals(key, 'Wrong key'); + + return { + key: witnessKey, + }; + } } -export type PLotteryType = InstanceType>; +// return PLottery; +// } + +// export type PLotteryType = InstanceType>; diff --git a/src/Proofs/TicketReduceProof.ts b/src/Proofs/TicketReduceProof.ts index c71445d..a2c5723 100644 --- a/src/Proofs/TicketReduceProof.ts +++ b/src/Proofs/TicketReduceProof.ts @@ -3,6 +3,7 @@ import { MerkleList, Poseidon, Provable, + Reducer, SelfProof, Struct, UInt64, @@ -80,44 +81,32 @@ export class MerkleActions extends MerkleList.create( export class TicketReduceProofPublicInput extends Struct({ action: LotteryAction, - roundWitness: MerkleMap20Witness, - roundTicketWitness: MerkleMap20Witness, - bankWitness: MerkleMap20Witness, - bankValue: Field, + ticketWitness: MerkleMap20Witness, }) {} export class TicketReduceProofPublicOutput extends Struct({ - initialState: Field, finalState: Field, initialTicketRoot: Field, - initialBankRoot: Field, - initialTicketId: Field, + initialBank: Field, newTicketRoot: Field, - newBankRoot: Field, + newBank: Field, processedActionList: Field, - lastProcessedRound: Field, lastProcessedTicketId: Field, }) {} export const init = async ( input: TicketReduceProofPublicInput, - initialState: Field, initialTicketRoot: Field, - initialBankRoot: Field, - initialRound: Field, - initialTicketId: Field + initialBank: Field ): Promise => { return new TicketReduceProofPublicOutput({ - initialState, - finalState: initialState, + finalState: Reducer.initialActionState, initialTicketRoot, - initialBankRoot, - initialTicketId, + initialBank, newTicketRoot: initialTicketRoot, - newBankRoot: initialBankRoot, + newBank: initialBank, processedActionList: ActionList.emptyHash, - lastProcessedRound: initialRound, - lastProcessedTicketId: initialTicketId, + lastProcessedTicketId: Field(-1), }); }; @@ -130,47 +119,25 @@ export const addTicket = async ( ): Promise => { prevProof.verify(); - let [prevRoundRoot, ticketId] = input.roundTicketWitness.computeRootAndKeyV2( + let [prevTicketRoot, ticketId] = input.ticketWitness.computeRootAndKeyV2( Field(0) ); - let [prevTicketRoot, round] = - input.roundWitness.computeRootAndKeyV2(prevRoundRoot); - - let expectedTicketId = Provable.if( - round.greaterThan(prevProof.publicOutput.lastProcessedRound), - Field(0), - prevProof.publicOutput.lastProcessedTicketId.add(1) - ); - + const expectedTicketId = prevProof.publicOutput.lastProcessedTicketId.add(1); ticketId.assertEquals(expectedTicketId, 'Wrong id for ticket'); prevTicketRoot.assertEquals( prevProof.publicOutput.newTicketRoot, 'Wrong ticket root' ); - round.assertEquals(input.action.round, 'Wrong round in witness'); // Update root - let [newTicketRoundRoot] = input.roundTicketWitness.computeRootAndKeyV2( + let [newTicketRoot] = input.ticketWitness.computeRootAndKeyV2( input.action.ticket.hash() ); - let [newTicketRoot] = - input.roundWitness.computeRootAndKeyV2(newTicketRoundRoot); - - let [prevBankRoot, bankKey] = input.bankWitness.computeRootAndKeyV2( - input.bankValue - ); - bankKey.assertEquals(round, 'Wrong bankKey'); - - prevBankRoot.assertEquals( - prevProof.publicOutput.newBankRoot, - 'Wrong bank root' - ); - - let [newBankRoot] = input.bankWitness.computeRootAndKeyV2( - input.bankValue.add(TICKET_PRICE.mul(input.action.ticket.amount).value) + let newBank = prevProof.publicOutput.newBank.add( + TICKET_PRICE.mul(input.action.ticket.amount).value ); let processedActionList = actionListAdd( @@ -179,15 +146,12 @@ export const addTicket = async ( ); return new TicketReduceProofPublicOutput({ - initialState: prevProof.publicOutput.initialState, finalState: prevProof.publicOutput.finalState, initialTicketRoot: prevProof.publicOutput.initialTicketRoot, - initialBankRoot: prevProof.publicOutput.initialBankRoot, - initialTicketId: prevProof.publicOutput.initialTicketId, + initialBank: prevProof.publicOutput.initialBank, newTicketRoot, - newBankRoot, + newBank, processedActionList, - lastProcessedRound: round, lastProcessedTicketId: expectedTicketId, }); }; @@ -208,15 +172,12 @@ export const cutActions = async ( let processedActionList = ActionList.emptyHash; return new TicketReduceProofPublicOutput({ - initialState: prevProof.publicOutput.initialState, finalState, initialTicketRoot: prevProof.publicOutput.initialTicketRoot, - initialBankRoot: prevProof.publicOutput.initialBankRoot, - initialTicketId: prevProof.publicOutput.initialTicketId, + initialBank: prevProof.publicOutput.initialBank, newTicketRoot: prevProof.publicOutput.newTicketRoot, - newBankRoot: prevProof.publicOutput.newBankRoot, + newBank: prevProof.publicOutput.newBank, processedActionList, - lastProcessedRound: prevProof.publicOutput.lastProcessedRound, lastProcessedTicketId: prevProof.publicOutput.lastProcessedTicketId, }); }; @@ -232,23 +193,13 @@ export const TicketReduceProgram = ZkProgram({ publicOutput: TicketReduceProofPublicOutput, methods: { init: { - privateInputs: [Field, Field, Field, Field, Field], + privateInputs: [Field, Field], async method( input: TicketReduceProofPublicInput, - initialState: Field, initialTicketRoot: Field, - initialBankRoot: Field, - initialRound: Field, - initialTicketId: Field + initialBankRoot: Field ): Promise { - return init( - input, - initialState, - initialTicketRoot, - initialBankRoot, - initialRound, - initialTicketId - ); + return init(input, initialTicketRoot, initialBankRoot); }, }, addTicket: { diff --git a/src/Random/RandomManager.ts b/src/Random/RandomManager.ts index 46a768e..2efd1b7 100644 --- a/src/Random/RandomManager.ts +++ b/src/Random/RandomManager.ts @@ -1,4 +1,5 @@ import { + Bool, Field, MerkleMap, MerkleMapWitness, @@ -9,6 +10,7 @@ import { Struct, UInt32, ZkProgram, + assert, method, state, } from 'o1js'; @@ -38,231 +40,140 @@ export class CommitValue extends Struct({ const { hashPart1, hashPart2 } = getIPFSCID(); -// Add events +const coordinatorAddress = ZkOnCoordinatorAddress; +const owner = PublicKey.empty(); // #TODO change with real owner address -export function getRandomManager( - owner: PublicKey, - coordinatorAddress: PublicKey = ZkOnCoordinatorAddress -) { - class RandomManager extends SmartContract { - @state(Field) commitRoot = State(); - @state(Field) resultRoot = State(); +export class RandomManager extends SmartContract { + @state(UInt32) startSlot = State(); + @state(Field) commit = State(); + @state(Field) result = State(); + @state(Field) curRandomValue = State(); - @state(Field) curRandomValue = State(); - @state(UInt32) startSlot = State(); + events = { + requested: ExternalRequestEvent, + }; - events = { - requested: ExternalRequestEvent, - }; + init() { + super.init(); - init() { - super.init(); - - this.commitRoot.set(emptyMapRoot); - this.resultRoot.set(emptyMapRoot); - } - - /** - * @notice Initial set of start slot. - * @dev It should be equal to startBlock on PLottery. Called only once. - * - * @param startSlot start slot value. - * - */ - @method async setStartSlot(startSlot: UInt32) { - this.permissionCheck(); - - this.startSlot.getAndRequireEquals().assertEquals(UInt32.from(0)); - this.startSlot.set(startSlot); - } - - /** - * @notice Commit hidden value. - * @dev Only hash o value and salt is stored. So value is hidden. - * - * @param commitValue Commit value = value + slot. - * @param commitWitness Witness of commit tree. - * - */ - @method async commit( - commitValue: CommitValue, - commitWitness: MerkleMapWitness - ) { - this.permissionCheck(); - - const [prevCommitRoot, round] = commitWitness.computeRootAndKeyV2( - Field(0) - ); - - this.checkRoundDoNotEnd(convertToUInt32(round)); - - this.commitRoot - .getAndRequireEquals() - .assertEquals(prevCommitRoot, 'commit: Wrong commit witness'); - - const [newCommitRoot] = commitWitness.computeRootAndKeyV2( - commitValue.hash() - ); - this.commitRoot.set(newCommitRoot); - } - - /** - * @notice Reveal number committed previously. - * @dev This function can be called only after oracle provided its random value - * - * @param commitValue Commit value = value + slot. - * @param commitWitness Witness of commit tree. - * @param resultWitness Witness of result tree. - * - */ - @method async reveal( - commitValue: CommitValue, - commitWitness: MerkleMapWitness, - resultWitness: MerkleMapWitness - ) { - this.permissionCheck(); - - // Check VRF computed - const curRandomValue = this.curRandomValue.getAndRequireEquals(); - curRandomValue.assertGreaterThan( - Field(0), - 'reveal: No random value in stash' - ); - - // Check commit witness - const [prevCommitRoot, round] = commitWitness.computeRootAndKeyV2( - commitValue.hash() - ); - - this.commitRoot - .getAndRequireEquals() - .assertEquals(prevCommitRoot, 'reveal: Wrong commit witness'); - - // Check result witness - const [prevResultRoot, resultRound] = resultWitness.computeRootAndKeyV2( - Field(0) - ); - - this.resultRoot - .getAndRequireEquals() - .assertEquals(prevResultRoot, 'reveal: wrong result witness'); - - round.assertEquals( - resultRound, - 'reveal: Round for commit and result should be equal' - ); - - // Check round is over - this.checkRoundPass(convertToUInt32(round)); - - // Compute result - const resultValue = Poseidon.hash([commitValue.value, curRandomValue]); - - // Update result - const [newResultRoot] = resultWitness.computeRootAndKeyV2(resultValue); - this.resultRoot.set(newResultRoot); + // assert( + // Bool(false), + // 'This contract is supposed to be deployed from factory. No init call there' + // ); + } - // Consume random value - this.curRandomValue.set(Field(0)); - } + /** + * @notice Commit hidden value. + * @dev Only hash o value and salt is stored. So value is hidden. + * + * @param commitValue Commit value = value + slot. + * + */ + @method async commitValue(commitValue: CommitValue) { + this.permissionCheck(); - /** - * @notice Sends request to ZKOn oracle. - * @dev Request body is stored on IPFS. - * - */ - @method async callZkon() { - let curRandomValue = this.curRandomValue.getAndRequireEquals(); - curRandomValue.assertEquals( - Field(0), - 'receiveZkonResponse: prev random value was not consumed. Call reveal first' - ); + const currentCommit = this.commit.getAndRequireEquals(); + currentCommit.assertEquals(Field(0), 'Already committed'); - const coordinator = new ZkonRequestCoordinator(coordinatorAddress); + this.commit.set(commitValue.hash()); + } + /* - const requestId = await coordinator.sendRequest( - this.address, - hashPart1, - hashPart2 - ); + /** + * @notice Reveal number committed previously. + * @dev This function can be called only after oracle provided its random value + * + * @param commitValue Commit value = value + slot. + * + */ + @method async reveal(commitValue: CommitValue) { + this.permissionCheck(); - const event = new ExternalRequestEvent({ - id: requestId, - hash1: hashPart1, - hash2: hashPart2, - }); + const result = this.result.getAndRequireEquals(); + result.assertEquals(Field(0), 'reveal: Result already computed'); - this.emitEvent('requested', event); - } + // Check VRF computed + const curRandomValue = this.curRandomValue.getAndRequireEquals(); + curRandomValue.assertGreaterThan(Field(0), 'reveal: No random value'); - /** - * @notice Callback function for ZKOn response - * - */ - @method - async receiveZkonResponse(requestId: Field, proof: ZkonProof) { - let curRandomValue = this.curRandomValue.getAndRequireEquals(); - curRandomValue.assertEquals( - Field(0), - 'receiveZkonResponse: prev random value was not consumed. Call reveal first' - ); + // Check commit + const commit = this.commit.getAndRequireEquals(); + commit.assertEquals(commitValue.hash(), 'reveal: wrong commit value'); - const coordinator = new ZkonRequestCoordinator(coordinatorAddress); - await coordinator.recordRequestFullfillment(requestId, proof); - this.curRandomValue.set(proof.publicInput.dataField); - } + // Check round is over + this.checkRoundPass(); - /** - * @notice Checks that sender is the owner of the contract. - * - */ - public permissionCheck() { - this.sender.getAndRequireSignature().assertEquals(owner); - } + // Compute result + const resultValue = Poseidon.hash([commitValue.value, curRandomValue]); - /** - * @notice Checks that specified round have already passed. - * - * @param round Round to check - */ - public checkRoundPass(round: UInt32) { - const startBlock = this.startSlot.getAndRequireEquals(); - this.network.globalSlotSinceGenesis.requireBetween( - startBlock.add(round.add(1).mul(BLOCK_PER_ROUND)), - UInt32.MAXINT() - ); - } + // Update result + this.result.set(resultValue); + } - /** - * @notice Checks that round have not ended yet - * - * @param round Round to check - */ - public checkRoundDoNotEnd(round: UInt32) { - const startBlock = this.startSlot.getAndRequireEquals(); - this.network.globalSlotSinceGenesis.requireBetween( - UInt32.from(0), - startBlock.add(round.add(1).mul(BLOCK_PER_ROUND).sub(1)) - ); - } + /** + * @notice Sends request to ZKOn oracle. + * @dev Request body is stored on IPFS. + * + */ + @method async callZkon() { + let curRandomValue = this.curRandomValue.getAndRequireEquals(); + curRandomValue.assertEquals( + Field(0), + 'random value have already been computed' + ); + + const coordinator = new ZkonRequestCoordinator(coordinatorAddress); + + const requestId = await coordinator.sendRequest( + this.address, + hashPart1, + hashPart2 + ); + + const event = new ExternalRequestEvent({ + id: requestId, + hash1: hashPart1, + hash2: hashPart2, + }); + + this.emitEvent('requested', event); } - return RandomManager; -} + /** + * @notice Callback function for ZKOn response + * + */ + @method + async receiveZkonResponse(requestId: Field, proof: ZkonProof) { + let curRandomValue = this.curRandomValue.getAndRequireEquals(); + curRandomValue.assertEquals( + Field(0), + 'receiveZkonResponse: prev random value was not consumed. Call reveal first' + ); + + const coordinator = new ZkonRequestCoordinator(coordinatorAddress); + await coordinator.recordRequestFullfillment(requestId, proof); + this.curRandomValue.set(proof.publicInput.dataField); + } -export function getMockedRandomManager(owner: PublicKey) { - class MockedRandomManager extends getRandomManager(owner, PublicKey.empty()) { - @method async mockReceiveZkonResponse(newValue: Field) { - this.curRandomValue.set(newValue); - } + /** + * @notice Checks that sender is the owner of the contract. + * + */ + public permissionCheck() { + this.sender.getAndRequireSignature().assertEquals(owner); } - return MockedRandomManager; + /** + * @notice Checks that specified round have already passed. + * + * @param round Round to check + */ + public checkRoundPass() { + const startSlot = this.startSlot.getAndRequireEquals(); + this.network.globalSlotSinceGenesis.requireBetween( + startSlot.add(BLOCK_PER_ROUND), + UInt32.MAXINT() + ); + } } - -export type RandomManagerType = InstanceType< - ReturnType ->; -export type MockedRandomManagerType = InstanceType< - ReturnType ->; diff --git a/src/StateManager/BaseStateManager.ts b/src/StateManager/BaseStateManager.ts index ff90b26..3e834e2 100644 --- a/src/StateManager/BaseStateManager.ts +++ b/src/StateManager/BaseStateManager.ts @@ -1,6 +1,5 @@ import { Field, JsonProof, MerkleMap, MerkleMapWitness, PublicKey } from 'o1js'; import { Ticket } from '../Structs/Ticket.js'; -import { getEmpty2dMerkleMap, getNullifierId } from '../util.js'; import { BLOCK_PER_ROUND, COMMISSION, @@ -18,6 +17,7 @@ import { // import { dummyBase64Proof } from 'o1js/dist/node/lib/proof-system/zkprogram'; // import { Pickles } from 'o1js/dist/node/snarky'; import { MerkleMap20, MerkleMap20Witness } from '../Structs/CustomMerkleMap.js'; +import { PLottery } from '../PLottery.js'; export async function mockProof( publicOutput: O, @@ -44,119 +44,47 @@ export async function mockProof( } export class BaseStateManager { + contract: PLottery; ticketMap: MerkleMap20; - roundTicketMap: MerkleMap20[]; - roundTickets: (Ticket | undefined)[][]; // Refunded ticket will be undefined - lastTicketInRound: number[]; + roundTickets: (Ticket | undefined)[]; // Refunded ticket will be undefined + lastTicketInRound: number; ticketNullifierMap: MerkleMap; - bankMap: MerkleMap20; - roundResultMap: MerkleMap20; - startBlock: Field; + startSlot: Field; isMock: boolean; shouldUpdateState: boolean; - dpProofs: { [key: number]: DistributionProof }; + dpProof: DistributionProof | undefined; constructor( - startBlock: Field, + contract: PLottery, isMock: boolean = true, shouldUpdateState: boolean = false ) { - this.ticketMap = getEmpty2dMerkleMap(20); - this.roundTicketMap = [new MerkleMap20()]; - this.lastTicketInRound = [0]; - this.roundTickets = [[]]; + this.contract = contract; + this.ticketMap = new MerkleMap20(); + this.lastTicketInRound = 0; + this.roundTickets = []; this.ticketNullifierMap = new MerkleMap(); - this.bankMap = new MerkleMap20(); - this.roundResultMap = new MerkleMap20(); - this.dpProofs = {}; - this.startBlock = startBlock; this.isMock = isMock; this.shouldUpdateState = shouldUpdateState; } - syncWithCurBlock(curBlock: number) { - let localRound = this.roundTicketMap.length - 1; - let curRound = Math.ceil((curBlock - +this.startBlock) / BLOCK_PER_ROUND); - - this.startNextRound(curRound - localRound); - } - - startNextRound(amount: number = 1) { - for (let i = 0; i < amount; i++) { - this.roundTicketMap.push(new MerkleMap20()); - this.lastTicketInRound.push(0); - this.roundTickets.push([]); - } - } - - getNextTicketWitness( - round: number - ): [MerkleMap20Witness, MerkleMap20Witness] { - const roundWitness = this.ticketMap.getWitness(Field.from(round)); - const ticketRoundWitness = this.roundTicketMap[round].getWitness( - Field.from(this.lastTicketInRound[round]) - ); - - return [roundWitness, ticketRoundWitness]; - } - - addTicket( - ticket: Ticket, - round: number - ): [MerkleMap20Witness, MerkleMap20Witness, MerkleMap20Witness, Field] { + addTicket(ticket: Ticket) { throw Error('Add ticket is not implemented'); } - // Returns witness and value - getBankWitness(round: number): [MerkleMap20Witness, Field] { - const bankWitness = this.bankMap.getWitness(Field.from(round)); - const value = this.bankMap.get(Field.from(round)); - - return [bankWitness, value]; - } - - updateResult( - round: number | Field, - result: Field - ): { - resultWitness: MerkleMap20Witness; - bankValue: Field; - bankWitness: MerkleMap20Witness; - } { - round = Field(round); - const resultWitness = this.roundResultMap.getWitness(round); - - const bankValue = this.bankMap.get(round); - const bankWitness = this.bankMap.getWitness(round); - - if (this.shouldUpdateState) { - this.roundResultMap.set(round, result); - this.bankMap.set( - round, - bankValue.mul(PRECISION - COMMISSION).div(PRECISION) - ); - } - - return { - resultWitness, - bankValue, - bankWitness, - }; - } - async getDP(round: number): Promise { - if (this.dpProofs[round]) { - return this.dpProofs[round]; + if (this.dpProof) { + return this.dpProof; } - const winningCombination = this.roundResultMap.get(Field.from(round)); - let ticketsInRound = this.lastTicketInRound[round]; + const winningCombination = this.contract.result.get(); + let ticketsInRound = this.lastTicketInRound; let curMap = new MerkleMap20(); let input = new DistributionProofPublicInput({ winningCombination, ticket: Ticket.random(PublicKey.empty()), - valueWitness: this.roundTicketMap[round].getWitness(Field(0)), + valueWitness: this.ticketMap.getWitness(Field(0)), }); let curProof = this.isMock @@ -164,7 +92,7 @@ export class BaseStateManager { : await DistributionProgram.init(input); for (let i = 0; i < ticketsInRound; i++) { - const ticket = this.roundTickets[round][i]; + const ticket = this.roundTickets[i]; if (!ticket) { // Skip refunded tickets continue; @@ -188,7 +116,7 @@ export class BaseStateManager { } } - this.dpProofs[round] = curProof; + this.dpProof = curProof; return curProof; } @@ -199,72 +127,42 @@ export class BaseStateManager { roundDP: JsonProof | undefined = undefined, ticketIndex: number = 1 // If two or more same tickets presented ): Promise<{ - roundWitness: MerkleMap20Witness; - roundTicketWitness: MerkleMap20Witness; + ticketWitness: MerkleMap20Witness; dp: DistributionProof; - winningNumbers: Field; - resultWitness: MerkleMap20Witness; - bankValue: Field; - bankWitness: MerkleMap20Witness; nullifierWitness: MerkleMapWitness; }> { - const roundWitness = this.ticketMap.getWitness(Field.from(round)); - const ticketHash = ticket.hash(); - let roundTicketWitness; + let ticketWitness; // Find ticket in tree let ticketId = 0; - for (; ticketId < this.lastTicketInRound[round]; ticketId++) { - if ( - this.roundTicketMap[round] - .get(Field(ticketId)) - .equals(ticketHash) - .toBoolean() - ) { + for (; ticketId < this.lastTicketInRound; ticketId++) { + if (this.ticketMap.get(Field(ticketId)).equals(ticketHash).toBoolean()) { ticketIndex--; if (ticketIndex == 0) { - roundTicketWitness = this.roundTicketMap[round].getWitness( - Field.from(ticketId) - ); + ticketWitness = this.ticketMap.getWitness(Field.from(ticketId)); break; } } } - if (!roundTicketWitness) { + if (!ticketWitness) { throw Error(`No such ticket in round ${round}`); } const dp = !roundDP ? await this.getDP(round) : await DistributionProof.fromJSON(roundDP); - const winningNumbers = this.roundResultMap.get(Field.from(round)); - if (winningNumbers.equals(Field(0)).toBoolean()) { - throw Error('Do not have a result for this round'); - } - const resultWitness = this.roundResultMap.getWitness(Field.from(round)); - - const bankValue = this.bankMap.get(Field.from(round)); - const bankWitness = this.bankMap.getWitness(Field.from(round)); const nullifierWitness = this.ticketNullifierMap.getWitness( - getNullifierId(Field.from(round), Field.from(ticketId)) + Field.from(ticketId) ); if (this.shouldUpdateState) { - this.ticketNullifierMap.set( - getNullifierId(Field.from(round), Field.from(ticketId)), - Field(1) - ); + this.ticketNullifierMap.set(Field.from(ticketId), Field(1)); } return { - roundWitness, - roundTicketWitness, + ticketWitness, dp, - winningNumbers, - resultWitness, - bankValue, - bankWitness, nullifierWitness, }; } @@ -273,57 +171,30 @@ export class BaseStateManager { round: number, ticket: Ticket ): Promise<{ - roundWitness: MerkleMap20Witness; - roundTicketWitness: MerkleMap20Witness; - resultWitness: MerkleMap20Witness; - bankValue: Field; - bankWitness: MerkleMap20Witness; + ticketWitness: MerkleMap20Witness; }> { - const roundWitness = this.ticketMap.getWitness(Field.from(round)); - const ticketHash = ticket.hash(); - let roundTicketWitness; + let ticketWitness; // Find ticket in tree let ticketId = 0; - for (; ticketId < this.lastTicketInRound[round]; ticketId++) { - if ( - this.roundTicketMap[round] - .get(Field(ticketId)) - .equals(ticketHash) - .toBoolean() - ) { - roundTicketWitness = this.roundTicketMap[round].getWitness( - Field.from(ticketId) - ); + for (; ticketId < this.lastTicketInRound; ticketId++) { + if (this.ticketMap.get(Field(ticketId)).equals(ticketHash).toBoolean()) { + ticketWitness = this.ticketMap.getWitness(Field.from(ticketId)); break; } } - if (!roundTicketWitness) { + if (!ticketWitness) { throw Error(`No such ticket in round ${round}`); } - const resultWitness = this.roundResultMap.getWitness(Field.from(round)); - - const bankValue = this.bankMap.get(Field.from(round)); - const bankWitness = this.bankMap.getWitness(Field.from(round)); - if (this.shouldUpdateState) { - this.roundTicketMap[round].set(Field(ticketId), Field(0)); - this.ticketMap.set(Field(round), this.roundTicketMap[round].getRoot()); + this.ticketMap.set(Field(ticketId), Field(0)); - this.roundTickets[round][ticketId] = undefined; - this.bankMap.set( - Field.from(round), - bankValue.sub(ticket.amount.mul(TICKET_PRICE).value) - ); + this.roundTickets[ticketId] = undefined; } return { - roundWitness, - roundTicketWitness, - resultWitness, - bankValue, - bankWitness, + ticketWitness, }; } } diff --git a/src/StateManager/FactoryStateManager.ts b/src/StateManager/FactoryStateManager.ts new file mode 100644 index 0000000..59878b3 --- /dev/null +++ b/src/StateManager/FactoryStateManager.ts @@ -0,0 +1,38 @@ +import { Field, MerkleMap, PublicKey } from 'o1js'; +import { PStateManager } from './PStateManager'; +import { PLottery } from '../PLottery'; +import { RandomManagerManager } from './RandomManagerManager'; + +interface IDeployInfo { + round: number; + randomManager: PublicKey; + plottery: PublicKey; +} + +export class FactoryManager { + roundsMap: MerkleMap; + deploys: { [key: number]: IDeployInfo }; + plotteryManagers: { [round: number]: PStateManager }; + randomManagers: { [round: number]: RandomManagerManager }; + + constructor() { + this.roundsMap = new MerkleMap(); + this.deploys = {}; + this.plotteryManagers = {}; + this.randomManagers = {}; + } + + addDeploy(round: number, randomManager: PublicKey, plottery: PublicKey) { + if (this.deploys[round]) { + throw Error(`Round ${round} already deployed`); + } + + this.deploys[round] = { round, randomManager, plottery }; + this.roundsMap.set(Field(round), Field(1)); + + const plotteryContract = new PLottery(plottery); + + this.plotteryManagers[round] = new PStateManager(plotteryContract); + this.randomManagers[round] = new RandomManagerManager(); + } +} diff --git a/src/StateManager/PStateManager.ts b/src/StateManager/PStateManager.ts index b651380..15d26a1 100644 --- a/src/StateManager/PStateManager.ts +++ b/src/StateManager/PStateManager.ts @@ -12,7 +12,7 @@ import { cutActions, } from '../Proofs/TicketReduceProof.js'; import { BaseStateManager } from './BaseStateManager.js'; -import { PLotteryType } from '../PLottery.js'; +import { PLottery } from '../PLottery.js'; export async function mockProof( publicOutput: O, @@ -39,19 +39,18 @@ export async function mockProof( } export class PStateManager extends BaseStateManager { - contract: PLotteryType; + contract: PLottery; processedTicketData: { ticketId: number; round: number; }; constructor( - plottery: PLotteryType, - startBlock: Field, + plottery: PLottery, isMock: boolean = true, shouldUpdateState: boolean = false ) { - super(startBlock, isMock, shouldUpdateState); + super(plottery, isMock, shouldUpdateState); this.contract = plottery; this.processedTicketData = { @@ -60,67 +59,29 @@ export class PStateManager extends BaseStateManager { }; } - override addTicket( - ticket: Ticket, - round: number, - forceUpdate: boolean = false - ): [MerkleMap20Witness, MerkleMap20Witness, MerkleMap20Witness, Field] { - const [roundWitness, ticketRoundWitness] = this.getNextTicketWitness(round); - const [bankWitness, bankValue] = this.getBankWitness(round); - + override addTicket(ticket: Ticket, forceUpdate: boolean = false) { if (this.shouldUpdateState || forceUpdate) { - this.roundTicketMap[round].set( - Field.from(this.lastTicketInRound[round]), - ticket.hash() - ); - this.ticketMap.set( - Field.from(round), - this.roundTicketMap[round].getRoot() - ); - - this.bankMap.set( - Field.from(round), - bankValue.add(TICKET_PRICE.mul(ticket.amount).value) - ); + this.ticketMap.set(Field.from(this.lastTicketInRound), ticket.hash()); } - this.roundTickets[round].push(ticket); - this.lastTicketInRound[round]++; - - return [roundWitness, ticketRoundWitness, bankWitness, bankValue]; + this.roundTickets.push(ticket); + this.lastTicketInRound++; } - async removeLastTicket(round: number) { - const ticket = this.roundTickets[round].pop()!; - this.lastTicketInRound[round]--; - const bankValue = this.bankMap.get(Field.from(round)); - this.roundTicketMap[round].set( - Field.from(this.lastTicketInRound[round] - 1), - Field(0) - ); - this.ticketMap.set(Field.from(round), this.roundTicketMap[round].getRoot()); - - this.bankMap.set( - Field.from(round), - bankValue.sub(TICKET_PRICE.mul(ticket.amount).value) - ); + async removeLastTicket() { + const ticket = this.roundTickets.pop()!; + this.lastTicketInRound--; + this.ticketMap.set(Field.from(this.lastTicketInRound - 1), Field(0)); // ? } async reduceTickets( - initialState?: Field, actionLists?: LotteryAction[][], updateState: boolean = true ): Promise { let addedTicketInfo = []; - if (!initialState) { - initialState = this.contract.lastProcessedState.get(); - } - if (!actionLists) { - actionLists = await this.contract.reducer.fetchActions({ - fromActionState: initialState, - }); + actionLists = await this.contract.reducer.fetchActions({}); } // All this params can be random for init function, because init do not use them @@ -129,36 +90,19 @@ export class PStateManager extends BaseStateManager { ticket: Ticket.random(this.contract.address), round: Field(0), }), - roundWitness: this.ticketMap.getWitness(Field(0)), - roundTicketWitness: this.roundTicketMap[0].getWitness(Field(0)), - bankWitness: this.bankMap.getWitness(Field(0)), - bankValue: Field(0), + ticketWitness: this.ticketMap.getWitness(Field(0)), }); let initialTicketRoot = this.ticketMap.getRoot(); - let initialBankRoot = this.bankMap.getRoot(); + let initialBank = this.contract.bank.get(); let curProof = this.isMock ? await mockProof( - await TRinit( - input, - initialState, - initialTicketRoot, - initialBankRoot, - Field.from(this.processedTicketData.round), - Field.from(this.processedTicketData.ticketId) - ), + await TRinit(input, initialTicketRoot, initialBank), TicketReduceProof, input ) - : await TicketReduceProgram.init( - input, - initialState, - initialTicketRoot, - initialBankRoot, - Field.from(this.processedTicketData.round), - Field.from(this.processedTicketData.ticketId) - ); + : await TicketReduceProgram.init(input, initialTicketRoot, initialBank); for (let actionList of actionLists) { for (let action of actionList) { @@ -177,12 +121,9 @@ export class PStateManager extends BaseStateManager { input = new TicketReduceProofPublicInput({ action: action, - roundWitness: this.ticketMap.getWitness(action.round), - roundTicketWitness: this.roundTicketMap[+action.round].getWitness( + ticketWitness: this.ticketMap.getWitness( Field(this.processedTicketData.ticketId) ), - bankWitness: this.bankMap.getWitness(action.round), - bankValue: this.bankMap.get(action.round), }); curProof = this.isMock @@ -193,7 +134,7 @@ export class PStateManager extends BaseStateManager { ) : await TicketReduceProgram.addTicket(input, curProof); - this.addTicket(action.ticket, +action.round, true); + this.addTicket(action.ticket, true); addedTicketInfo.push({ round: action.round, }); @@ -210,7 +151,7 @@ export class PStateManager extends BaseStateManager { } if (!updateState) { - addedTicketInfo.forEach((v) => this.removeLastTicket(+v.round)); + addedTicketInfo.forEach((v) => this.removeLastTicket()); } return curProof; diff --git a/src/StateManager/RandomManagerManager.ts b/src/StateManager/RandomManagerManager.ts index 99666a8..0c11842 100644 --- a/src/StateManager/RandomManagerManager.ts +++ b/src/StateManager/RandomManagerManager.ts @@ -1,7 +1,7 @@ /// Best fucking naming import { Field, MerkleMap, MerkleMapWitness } from 'o1js'; -import { CommitValue } from '../Random/RandomManager'; +import { CommitValue, RandomManager } from '../Random/RandomManager'; interface WitnessedValue { value: Field; @@ -9,74 +9,24 @@ interface WitnessedValue { } export class RandomManagerManager { - commitMap: MerkleMap; - resultMap: MerkleMap; - commits: { [key: number]: CommitValue }; - results: { [key: number]: Field }; + commit: CommitValue | undefined; - constructor() { - this.commitMap = new MerkleMap(); - this.resultMap = new MerkleMap(); - this.commits = {}; - this.results = {}; - } - - getCommitWitness(round: number | Field): WitnessedValue { - round = Field(round); - - return { - value: this.commitMap.get(round), - witness: this.commitMap.getWitness(round), - }; - } + constructor() {} - addCommit(round: number | Field, commit: CommitValue) { - round = Field(round); - if (this.commits[+round]) { - throw Error(`You have already committed to round ${+round}`); + addCommit(commit: CommitValue) { + if (this.commit) { + throw Error(`You have already committed to round}`); } - this.commits[+round] = commit; - this.commitMap.set(round, commit.hash()); - } - - getResultWitness(round: number | Field): WitnessedValue { - round = Field(round); - - return { - value: this.resultMap.get(round), - witness: this.resultMap.getWitness(round), - }; - } - - addResultValue(round: number | Field, value: Field) { - round = Field(round); - - if (this.results[+round]) { - throw Error(`You already have result in round: ${+round}`); - } - - this.results[+round] = value; - - this.resultMap.set(round, value); + this.commit = commit; } toJSON(): string { const json = { - commits: Object.entries(this.commits).map(([round, commitValue]) => { - return { - round, - value: commitValue.value.toString(), - salt: commitValue.salt.toString(), - }; - }), - - results: Object.entries(this.results).map(([round, resultValue]) => { - return { - round, - result: resultValue.toString(), - }; - }), + commit: { + salt: this.commit?.salt.toString(), + value: this.commit?.value.toString(), + }, }; return JSON.stringify(json); @@ -87,19 +37,12 @@ export class RandomManagerManager { const res = new RandomManagerManager(); - data.commits.forEach((commit: any) => { - res.addCommit( - Field(commit.round), - new CommitValue({ - value: Field(commit.value), - salt: Field(commit.salt), - }) - ); - }); - - data.results.forEach((result: any) => { - res.addResultValue(Field(result.round), Field(result.result)); - }); + res.addCommit( + new CommitValue({ + value: Field(data.commit.value), + salt: Field(data.commit.salt), + }) + ); return res; } diff --git a/src/Tests/MockedContracts/MockedFactory.ts b/src/Tests/MockedContracts/MockedFactory.ts new file mode 100644 index 0000000..240abab --- /dev/null +++ b/src/Tests/MockedContracts/MockedFactory.ts @@ -0,0 +1,137 @@ +import { + AccountUpdate, + Bool, + Field, + MerkleMap, + MerkleMapWitness, + Poseidon, + PublicKey, + SmartContract, + State, + state, + Struct, + UInt64, + Permissions, + method, + Cache, +} from 'o1js'; +import { BLOCK_PER_ROUND } from '../../constants'; +import { MerkleMap20 } from '../../Structs/CustomMerkleMap'; +import { RandomManager } from '../../Random/RandomManager'; +import { PLottery } from '../../PLottery'; +import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; +import { TicketReduceProgram } from '../../Proofs/TicketReduceProof'; +import { DistributionProgram } from '../../Proofs/DistributionProof'; +import { MockedRandomManager } from './MockedRandomManager'; + +const emptyMerkleMapRoot = new MerkleMap().getRoot(); + +await ZkonZkProgram.compile({ cache: Cache.FileSystem('cache') }); +await ZkonRequestCoordinator.compile({ cache: Cache.FileSystem('cache') }); +const { verificationKey: mockedRandomManagerVK } = + await MockedRandomManager.compile(); +await TicketReduceProgram.compile({ cache: Cache.FileSystem('cache') }); +await DistributionProgram.compile({ cache: Cache.FileSystem('cache') }); +const { verificationKey: PLotteryVK } = await PLottery.compile({ + cache: Cache.FileSystem('cache'), +}); + +class RoundInfo extends Struct({ + startSlot: Field, + randomManagerAddress: PublicKey, +}) {} + +export class DeployEvent extends Struct({ + round: Field, + randomManager: PublicKey, + plottery: PublicKey, +}) {} + +const startSlot = Field(0); + +///Just copy with other vk for random manager +export class MockedPlotteryFactory extends SmartContract { + events = { + 'deploy-plottery': DeployEvent, + }; + + @state(Field) roundsRoot = State(); + + init() { + this.roundsRoot.set(emptyMerkleMapRoot); + } + + @method + async deployRound( + witness: MerkleMapWitness, + randomManager: PublicKey, + plottery: PublicKey + ) { + // Check if round was not used and update merkle map + const curRoot = this.roundsRoot.getAndRequireEquals(); + const [expectedRoot, round] = witness.computeRootAndKeyV2(Field(0)); + curRoot.assertEquals(expectedRoot, 'Wrong witness'); + + const [newRoot] = witness.computeRootAndKeyV2(Field(1)); + this.roundsRoot.set(newRoot); + + // Deploy and initialize random manager + { + const rmUpdate = AccountUpdate.createSigned(randomManager); + rmUpdate.account.verificationKey.set(mockedRandomManagerVK); + rmUpdate.update.appState[0] = { + isSome: Bool(true), + value: startSlot, + }; + + // Update permissions + rmUpdate.body.update.permissions = { + isSome: Bool(true), + value: { + ...Permissions.default(), + }, + }; + } + // Deploy plottery + { + const plotteryUpdate = AccountUpdate.createSigned(plottery); + plotteryUpdate.account.verificationKey.set(PLotteryVK); + // Start slot set + plotteryUpdate.update.appState[0] = { + isSome: Bool(true), + value: startSlot, + }; + // Set random manager + plotteryUpdate.update.appState[1] = { + isSome: Bool(true), + value: randomManager.x, // ? + }; + + // Set ticket ticketRoot + plotteryUpdate.update.appState[2] = { + isSome: Bool(true), + value: new MerkleMap20().getRoot(), + }; + + // Set ticket nullifier + plotteryUpdate.update.appState[3] = { + isSome: Bool(true), + value: new MerkleMap20().getRoot(), + }; + + // Update permissions + plotteryUpdate.body.update.permissions = { + isSome: Bool(true), + value: { + ...Permissions.default(), + }, + }; + } + + // Emit event + this.emitEvent( + 'deploy-plottery', + new DeployEvent({ round, plottery, randomManager }) + ); + } +} diff --git a/src/Tests/MockedContracts/MockedRandomManager.ts b/src/Tests/MockedContracts/MockedRandomManager.ts new file mode 100644 index 0000000..b1d2cf1 --- /dev/null +++ b/src/Tests/MockedContracts/MockedRandomManager.ts @@ -0,0 +1,8 @@ +import { Field, method } from 'o1js'; +import { RandomManager } from '../../Random/RandomManager'; + +export class MockedRandomManager extends RandomManager { + @method async mockReceiveZkonResponse(randomValue: Field) { + this.curRandomValue.set(randomValue); + } +} diff --git a/src/Tests/Random.test.ts b/src/Tests/Random.test.ts index 1a54f12..fb37de9 100644 --- a/src/Tests/Random.test.ts +++ b/src/Tests/Random.test.ts @@ -10,7 +10,6 @@ import { UInt32, UInt64, } from 'o1js'; -import { PLotteryType, generateNumbersSeed, getPLottery } from '../PLottery'; import { Ticket } from '../Structs/Ticket'; import { NumberPacked, convertToUInt64 } from '../util'; import { @@ -19,15 +18,16 @@ import { ZkOnCoordinatorAddress, treasury, } from '../constants'; -import { - CommitValue, - MockedRandomManagerType, - RandomManagerType, - getMockedRandomManager, - getRandomManager, -} from '../Random/RandomManager'; import { RandomManagerManager } from '../StateManager/RandomManagerManager'; import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; +import { CommitValue, RandomManager } from '../Random/RandomManager'; +import { PlotteryFactory } from '../Factory'; +import { FactoryManager } from '../StateManager/FactoryStateManager'; +import { PLottery } from '../PLottery'; +import { TicketReduceProgram } from '../Proofs/TicketReduceProof'; +import { DistributionProgram } from '../Proofs/DistributionProof'; +import { MockedRandomManager } from './MockedContracts/MockedRandomManager'; +import { MockedPlotteryFactory } from './MockedContracts/MockedFactory'; const testCommitValues = [...Array(10)].map( (_, i) => new CommitValue({ value: Field(i), salt: Field.random() }) @@ -48,8 +48,11 @@ describe('Add', () => { senderKey: PrivateKey, randomManagerAddress: PublicKey, randomManagerPrivateKey: PrivateKey, - randomManager: MockedRandomManagerType, - rmStateManager: RandomManagerManager, + factoryAddress: PublicKey, + factoryPrivateKey: PrivateKey, + factory: PlotteryFactory, + randomManager: MockedRandomManager, + factoryManager: FactoryManager, mineNBlocks: (n: number) => void, commitValue: (round: number, commitValue: CommitValue) => Promise, produceResultInRM: ( @@ -73,23 +76,25 @@ describe('Add', () => { senderKey = senderAccount.key; randomManagerPrivateKey = PrivateKey.random(); randomManagerAddress = randomManagerPrivateKey.toPublicKey(); - let RandomManager = getMockedRandomManager(deployerAccount); - randomManager = new RandomManager(randomManagerAddress); - - rmStateManager = new RandomManagerManager(); + factoryPrivateKey = PrivateKey.random(); + factoryAddress = factoryPrivateKey.toPublicKey(); + randomManager = new MockedRandomManager(randomManagerAddress); + // randomManager = new RandomManager(randomManagerAddress); + // factory = new MockedPlotteryFactory(factoryAddress); + factory = new MockedPlotteryFactory(factoryAddress); + + factoryManager = new FactoryManager(); mineNBlocks = (n: number) => { let curAmount = Local.getNetworkState().globalSlotSinceGenesis; Local.setGlobalSlot(curAmount.add(n)); }; - commitValue = async (round: number, commitValue: CommitValue) => { - const commitWV = rmStateManager.getCommitWitness(round); let tx = Mina.transaction(deployerAccount, async () => { - randomManager.commit(commitValue, commitWV.witness); + randomManager.commitValue(commitValue); }); await tx.prove(); await tx.sign([deployerKey]).send(); - rmStateManager.addCommit(round, commitValue); + factoryManager.randomManagers[round].addCommit(commitValue); }; produceResultInRM = async ( round: number, @@ -101,81 +106,77 @@ describe('Add', () => { }); await tx.prove(); await tx.sign([deployerKey]).send(); - const commitWV = rmStateManager.getCommitWitness(round); - const resultWV = rmStateManager.getResultWitness(round); let tx2 = Mina.transaction(deployerAccount, async () => { - randomManager.reveal(commitValue, commitWV.witness, resultWV.witness); + randomManager.reveal(commitValue); }); await tx2.prove(); await tx2.sign([deployerKey]).send(); - rmStateManager.addResultValue( - round, - Poseidon.hash([commitValue.value, vrfValue]) - ); }; }); async function localDeploy() { + // // Factory deploy const txn = await Mina.transaction(deployerAccount, async () => { AccountUpdate.fundNewAccount(deployerAccount); - await randomManager.deploy(); + await factory.deploy(); }); await txn.prove(); // this tx needs .sign(), because `deploy()` adds an account update that requires signature authorization - await txn.sign([deployerKey, randomManagerPrivateKey]).send(); + await txn.sign([deployerKey, factoryPrivateKey]).send(); + let roundWitness = factoryManager.roundsMap.getWitness(Field(0)); + let plotteryKey = PrivateKey.randomKeypair(); + let plotteryAddress = plotteryKey.publicKey; // Do not need it for now + const tx2 = await Mina.transaction(deployerAccount, async () => { + AccountUpdate.fundNewAccount(deployerAccount); + AccountUpdate.fundNewAccount(deployerAccount); + await factory.deployRound( + roundWitness, + randomManagerAddress, + plotteryAddress // Do not need it for now + ); + }); + await tx2.prove(); + await tx2 + .sign([deployerKey, plotteryKey.privateKey, randomManagerPrivateKey]) + .send(); + factoryManager.addDeploy(0, randomManagerAddress, plotteryAddress); } - it('Should produce random value', async () => { + it.only('Initial state check', async () => { await localDeploy(); - expect(rmStateManager.commitMap.getRoot()).toEqual( - randomManager.commitRoot.get() + expect(factory.roundsRoot.get()).toEqual( + factoryManager.roundsMap.getRoot() ); - expect(rmStateManager.resultMap.getRoot()).toEqual( - randomManager.resultRoot.get() - ); + expect(randomManager.startSlot.get()).toEqual(UInt32.from(0)); + expect(randomManager.commit.get()).toEqual(Field(0)); + expect(randomManager.result.get()).toEqual(Field(0)); + expect(randomManager.curRandomValue.get()).toEqual(Field(0)); + }); + + it('Should produce random value', async () => { + await localDeploy(); for (let i = 0; i < 10; i++) { console.log(i); await commitValue(i, testCommitValues[i]); - expect(rmStateManager.commitMap.getRoot()).toEqual( - randomManager.commitRoot.get() - ); - mineNBlocks(BLOCK_PER_ROUND + 1); await produceResultInRM(i, testVRFValues[i], testCommitValues[i]); const seed = Poseidon.hash([testCommitValues[i].value, testVRFValues[i]]); - expect(rmStateManager.resultMap.get(Field(i))).toEqual(seed); - expect(rmStateManager.resultMap.getRoot()).toEqual( - randomManager.resultRoot.get() - ); + expect(randomManager.result.get()).toEqual(seed); } }); - it('JSON works', async () => { - for (let i = 0; i < testCommitValues.length; i++) { - rmStateManager.addCommit(i, testCommitValues[i]); - rmStateManager.addResultValue(i, testVRFValues[i]); - } - - let json = rmStateManager.toJSON(); - - let copy = RandomManagerManager.fromJSON(json); + // it('JSON works', async () => { + // rmStateManager.addCommit(testCommitValues[0]); - expect(rmStateManager.commitMap.getRoot()).toEqual( - copy.commitMap.getRoot() - ); + // let json = rmStateManager.toJSON(); - expect(rmStateManager.resultMap.getRoot()).toEqual( - copy.resultMap.getRoot() - ); + // let copy = RandomManagerManager.fromJSON(json); - for (let i = 0; i < testCommitValues.length; i++) { - expect(rmStateManager.commits[i].hash()).toEqual(copy.commits[i].hash()); - expect(rmStateManager.results[i]).toEqual(copy.results[i]); - } - }); + // expect(rmStateManager.commit).toEqual(copy.commit); + // }); }); diff --git a/src/util.ts b/src/util.ts index 78cd53d..a504da5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,40 +2,8 @@ import { Field, Gadgets, MerkleMap, Poseidon, UInt32, UInt64 } from 'o1js'; import { MerkleMap20 } from './Structs/CustomMerkleMap.js'; import { PackedUInt32Factory } from 'o1js-pack'; -export const getEmpty2dMerkleMap = (height?: number): MerkleMap => { - let emptyMapRoot; - let empty2dMap; - if (height) { - if (height != 20) { - throw Error('Custom size merkle map is not supported yet. Only 20'); - } - emptyMapRoot = new MerkleMap20().getRoot(); - empty2dMap = new MerkleMap20(); - } else { - emptyMapRoot = new MerkleMap().getRoot(); - empty2dMap = new MerkleMap(); - } - - empty2dMap.tree.zeroes[0] = emptyMapRoot; - for (let i = 1; i < empty2dMap.tree.height; i++) { - empty2dMap.tree.zeroes[i] = Poseidon.hash([ - empty2dMap.tree.zeroes[i - 1], - empty2dMap.tree.zeroes[i - 1], - ]); - } - - return empty2dMap as MerkleMap; -}; - export class NumberPacked extends PackedUInt32Factory() {} -export function getNullifierId(round: Field, ticketId: Field): Field { - Gadgets.rangeCheck64(round); - Gadgets.rangeCheck64(ticketId); - - return Field.fromBits([...round.toBits(64), ...ticketId.toBits(64)]); -} - export function convertToUInt64(value: Field): UInt64 { let val = UInt64.Unsafe.fromField(value); UInt64.check(val); From 859a25656fb47a83400d131e3c99a09714d75cfc Mon Sep 17 00:00:00 2001 From: aii23 Date: Mon, 7 Oct 2024 10:13:04 +0700 Subject: [PATCH 2/6] Single random test done --- src/Random/RandomManager.ts | 2 +- src/Tests/Random.test.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Random/RandomManager.ts b/src/Random/RandomManager.ts index 2efd1b7..bdee601 100644 --- a/src/Random/RandomManager.ts +++ b/src/Random/RandomManager.ts @@ -161,7 +161,7 @@ export class RandomManager extends SmartContract { * */ public permissionCheck() { - this.sender.getAndRequireSignature().assertEquals(owner); + // this.sender.getAndRequireSignature().assertEquals(owner); } /** diff --git a/src/Tests/Random.test.ts b/src/Tests/Random.test.ts index fb37de9..f501d13 100644 --- a/src/Tests/Random.test.ts +++ b/src/Tests/Random.test.ts @@ -141,7 +141,7 @@ describe('Add', () => { factoryManager.addDeploy(0, randomManagerAddress, plotteryAddress); } - it.only('Initial state check', async () => { + it('Initial state check', async () => { await localDeploy(); expect(factory.roundsRoot.get()).toEqual( @@ -157,17 +157,18 @@ describe('Add', () => { it('Should produce random value', async () => { await localDeploy(); - for (let i = 0; i < 10; i++) { - console.log(i); - await commitValue(i, testCommitValues[i]); + // for (let i = 0; i < 10; i++) { + let i = 0; + // console.log(i); + await commitValue(i, testCommitValues[i]); - mineNBlocks(BLOCK_PER_ROUND + 1); + mineNBlocks(BLOCK_PER_ROUND + 1); - await produceResultInRM(i, testVRFValues[i], testCommitValues[i]); + await produceResultInRM(i, testVRFValues[i], testCommitValues[i]); - const seed = Poseidon.hash([testCommitValues[i].value, testVRFValues[i]]); - expect(randomManager.result.get()).toEqual(seed); - } + const seed = Poseidon.hash([testCommitValues[i].value, testVRFValues[i]]); + expect(randomManager.result.get()).toEqual(seed); + // } }); // it('JSON works', async () => { From 97dc73f492eacc2aa03ee9ac2f664b8cf625948a Mon Sep 17 00:00:00 2001 From: aii23 Date: Tue, 8 Oct 2024 14:58:57 +0700 Subject: [PATCH 3/6] Some modifiactions --- scripts/deploy_1rnd_pottery.ts | 94 ++++++ scripts/deploy_factory.ts | 46 +++ src/Factory.ts | 34 +- src/PLottery.ts | 60 +--- src/Proofs/TicketReduceProof.ts | 1 - src/Random/RandomManager.ts | 16 +- src/StateManager/BaseStateManager.ts | 7 +- src/StateManager/FactoryStateManager.ts | 12 +- src/StateManager/PStateManager.ts | 20 +- src/Tests/MockedContracts/MockedFactory.ts | 28 +- src/Tests/PLottery.test.ts | 345 +++++++++------------ 11 files changed, 371 insertions(+), 292 deletions(-) create mode 100644 scripts/deploy_1rnd_pottery.ts create mode 100644 scripts/deploy_factory.ts diff --git a/scripts/deploy_1rnd_pottery.ts b/scripts/deploy_1rnd_pottery.ts new file mode 100644 index 0000000..1029e3a --- /dev/null +++ b/scripts/deploy_1rnd_pottery.ts @@ -0,0 +1,94 @@ +import { Field, Mina, PrivateKey, PublicKey } from 'o1js'; +import { DeployEvent, PlotteryFactory } from '../src/Factory'; +import { FactoryManager } from '../src/StateManager/FactoryStateManager'; +import { configDefaultInstance } from './utils'; +import * as fs from 'fs'; + +configDefaultInstance(); + +let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); +let deployer = deployerKey.toPublicKey(); + +let from = process.argv[2]; + +let to = process.argv[3]; + +if (!from || !to) { + throw Error(`You should provide from round and to round`); +} + +let { verificationKey } = await PlotteryFactory.compile(); + +const factoryManager = new FactoryManager(); + +let factoryAddress: PublicKey; + +const factoryDataPath = `./deployV2/${verificationKey.hash.toString()}/factory.json`; +if (fs.existsSync(factoryDataPath)) { + let factoryData = fs.readFileSync(factoryDataPath); + factoryAddress = PublicKey.fromBase58( + JSON.parse(factoryData.toString()).address + ); +} else { + throw Error(`No factory deployment found. Deploy it first`); +} + +let factory = new PlotteryFactory(factoryAddress); + +let factoryEvents = await factory.fetchEvents(); + +let deployments; + +const deploymentsPath = `./deployV2/${verificationKey.hash.toString()}/deployments.json`; + +if (fs.existsSync(deploymentsPath)) { + let deploymentsBuffer = fs.readFileSync(deploymentsPath); + deployments = JSON.parse(deploymentsBuffer.toString()); +} else { + deployments = {}; +} + +// Restore state of factoryManager +for (const event of factoryEvents) { + let deployEvent = event.event.data as any; + + factoryManager.addDeploy( + +deployEvent.round, + deployEvent.randomManager, + deployEvent.plottery + ); +} + +for (let round = +from; round <= +to; round++) { + let witness = factoryManager.roundsMap.getWitness(Field(round)); + + let plotteryPrivateKey = PrivateKey.random(); + let plotteryAddress = plotteryPrivateKey.toPublicKey(); + + let randomManagerPrivateKey = PrivateKey.random(); + let randomManagerAddress = randomManagerPrivateKey.toPublicKey(); + + let tx = Mina.transaction(deployer, async () => { + await factory.deployRound(witness, randomManagerAddress, plotteryAddress); + }); + + await tx.prove(); + await tx + .sign([deployerKey, randomManagerPrivateKey, plotteryPrivateKey]) + .send(); + + deployments[round] = { + randomManager: randomManagerAddress.toBase58(), + plottery: plotteryAddress.toBase58(), + }; +} + +// Write result to file + +if (!fs.existsSync(`./deployV2/${verificationKey.hash.toString()}`)) { + fs.mkdirSync(`./deployV2/${verificationKey.hash.toString()}`, { + recursive: true, + }); +} + +fs.writeFileSync(deploymentsPath, JSON.stringify(deployments, null, 2)); diff --git a/scripts/deploy_factory.ts b/scripts/deploy_factory.ts new file mode 100644 index 0000000..107ead7 --- /dev/null +++ b/scripts/deploy_factory.ts @@ -0,0 +1,46 @@ +import { AccountUpdate, Mina, PrivateKey } from 'o1js'; +import { configDefaultInstance } from './utils'; +import { PlotteryFactory } from '../src/Factory'; +import * as fs from 'fs'; + +configDefaultInstance(); + +let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); +let deployer = deployerKey.toPublicKey(); + +let { verificationKey } = await PlotteryFactory.compile(); + +const factoryDataPath = `./deployV2/${verificationKey.hash.toString()}/factory.json`; + +if (fs.existsSync(factoryDataPath)) { + throw Error('Contract with same verification key already deployed'); +} + +let factoryKey = PrivateKey.random(); +let factoryAddress = factoryKey.toPublicKey(); + +let factory = new PlotteryFactory(factoryAddress); + +let tx = Mina.transaction(deployer, async () => { + AccountUpdate.fundNewAccount(deployer); + factory.deploy(); +}); + +await tx.prove(); +await tx.sign([deployerKey, factoryKey]).send(); + +let deploymentData = { + address: factoryAddress.toBase58(), + key: factoryKey.toBase58(), +}; + +if (!fs.existsSync(`./deployV2/${verificationKey.hash.toString()}`)) { + fs.mkdirSync(`./deployV2/${verificationKey.hash.toString()}`, { + recursive: true, + }); +} + +fs.writeFileSync( + `./deployV2/${verificationKey.hash.toString()}/factory.json`, + JSON.stringify(deploymentData, null, 2) +); diff --git a/src/Factory.ts b/src/Factory.ts index 09f3c36..9c566a6 100644 --- a/src/Factory.ts +++ b/src/Factory.ts @@ -14,6 +14,7 @@ import { Permissions, method, Cache, + UInt32, } from 'o1js'; import { BLOCK_PER_ROUND } from './constants'; import { MerkleMap20 } from './Structs/CustomMerkleMap'; @@ -27,11 +28,7 @@ const emptyMerkleMapRoot = new MerkleMap().getRoot(); await ZkonZkProgram.compile({ cache: Cache.FileSystem('cache') }); await ZkonRequestCoordinator.compile({ cache: Cache.FileSystem('cache') }); -const { verificationKey: randomManagerVK } = await RandomManager.compile({ - cache: Cache.FileSystem('cache'), -}); -// const { verificationKey: mockedRandomManagerVK } = -// await MockedRandomManager.compile(); +const { verificationKey: randomManagerVK } = await RandomManager.compile(); await TicketReduceProgram.compile({ cache: Cache.FileSystem('cache') }); await DistributionProgram.compile({ cache: Cache.FileSystem('cache') }); const { verificationKey: PLotteryVK } = await PLottery.compile({ @@ -51,6 +48,7 @@ export class DeployEvent extends Struct({ const startSlot = Field(0); +///Just copy with other vk for random manager export class PlotteryFactory extends SmartContract { events = { 'deploy-plottery': DeployEvent, @@ -77,13 +75,15 @@ export class PlotteryFactory extends SmartContract { const [newRoot] = witness.computeRootAndKeyV2(Field(1)); this.roundsRoot.set(newRoot); + const localStartSlot = startSlot.add(round.mul(BLOCK_PER_ROUND)); + // Deploy and initialize random manager { const rmUpdate = AccountUpdate.createSigned(randomManager); rmUpdate.account.verificationKey.set(randomManagerVK); rmUpdate.update.appState[0] = { isSome: Bool(true), - value: startSlot, + value: localStartSlot, }; // Update permissions @@ -98,25 +98,35 @@ export class PlotteryFactory extends SmartContract { { const plotteryUpdate = AccountUpdate.createSigned(plottery); plotteryUpdate.account.verificationKey.set(PLotteryVK); - // Start slot set + + // Set random manager + const rmFields = randomManager.toFields(); + + // Random manager address plotteryUpdate.update.appState[0] = { isSome: Bool(true), - value: startSlot, + value: rmFields[0], }; - // Set random manager + plotteryUpdate.update.appState[1] = { isSome: Bool(true), - value: randomManager.x, // ? + value: rmFields[1], }; - // Set ticket ticketRoot + // Start slot set plotteryUpdate.update.appState[2] = { + isSome: Bool(true), + value: localStartSlot, + }; + + // Set ticket ticketRoot + plotteryUpdate.update.appState[3] = { isSome: Bool(true), value: new MerkleMap20().getRoot(), }; // Set ticket nullifier - plotteryUpdate.update.appState[3] = { + plotteryUpdate.update.appState[4] = { isSome: Bool(true), value: new MerkleMap20().getRoot(), }; diff --git a/src/PLottery.ts b/src/PLottery.ts index eea0576..2cb339d 100644 --- a/src/PLottery.ts +++ b/src/PLottery.ts @@ -70,22 +70,18 @@ const emptyMap20Root = new MerkleMap20().getRoot(); export class BuyTicketEvent extends Struct({ ticket: Ticket, - round: Field, }) {} export class ProduceResultEvent extends Struct({ result: Field, - round: Field, }) {} export class GetRewardEvent extends Struct({ ticket: Ticket, - round: Field, }) {} export class RefundEvent extends Struct({ ticket: Ticket, - round: Field, }) {} export class ReduceEvent extends Struct({}) {} @@ -121,19 +117,13 @@ export class PLottery extends SmartContract { // Questionable @state(Bool) reduced = State(); - // // Round in witch last reduce happened - // @state(Field) lastReduceInRound = State(); - - // // Last processed ticketId by reducer - // @state(Field) lastProcessedTicketId = State(); - init() { super.init(); /// !!!! This contracts is deployed from factory. No init call there this.ticketRoot.set(emptyMap20Root); - this.ticketNullifier.set(emptyMapRoot); + this.ticketNullifier.set(emptyMap20Root); this.startSlot.set( this.network.globalSlotSinceGenesis.getAndRequireEquals() ); @@ -159,15 +149,13 @@ export class PLottery extends SmartContract { * @dev No ticket merkle tree update happens here. Only action is dispatched. * * @param ticket The lottery ticket being purchased. - * @param round The lottery round for which the ticket is being purchased. * - * @require The sender must be the owner of the ticket. * @require The ticket must be valid as per the ticket validity check. * @require The specified round must be the current lottery round. * * @event buy-ticket Emitted when a ticket is successfully purchased. */ - @method async buyTicket(ticket: Ticket, round: Field) { + @method async buyTicket(ticket: Ticket) { // Ticket validity check ticket.check().assertTrue(); @@ -190,14 +178,12 @@ export class PLottery extends SmartContract { this.reducer.dispatch( new LotteryAction({ ticket, - round, // #TODO remove }) ); this.emitEvent( 'buy-ticket', new BuyTicketEvent({ ticket, - round: round, // #TODO remove }) ); } @@ -263,16 +249,8 @@ export class PLottery extends SmartContract { * @dev Random number seed is taken from RandomManager contract for this round. * Then using this seed 6 number is generated and stored * - * @param resultWitness The Merkle proof witness for the current result tree. - * @param bankValue The current value in the bank for this round. - * @param bankWitness The Merkle proof witness for the bank tree. - * @param rmWitness The Merkle proof witness for the random value tree(tree is stored on RandomManager contract). - * @param rmValue The random value used to generate winning numbers. - * - * @require The result for this round must not have been computed yet. - * @require The provided result witness must be valid and match the initial result root. + * @require The result must not have been computed yet. * @require The round must have been reduced before the result can be computed. - * @require The random value should match one, that is stored on RandomManager contract. * * @event produce-result Emitted when the result is successfully produced and the result tree is updated. */ @@ -305,7 +283,6 @@ export class PLottery extends SmartContract { 'produce-result', new ProduceResultEvent({ result: resultPacked, - round: Field(0), // #TODO remove }) ); } @@ -318,11 +295,7 @@ export class PLottery extends SmartContract { * and updating the necessary states. * * @param ticket The lottery ticket for which the refund is being requested. - * @param roundWitness The 1st level Merkle proof witness for the tickets tree. - * @param roundTicketWitness The 2nd level Merkle proof witness for the round's ticket tree. - * @param resultWitness The Merkle proof witness for the result tree. - * @param bankValue The value of bank for that round. - * @param bankWitness The Merkle proof witness for bank tree. + * @param ticketWitness Witness of the ticket in the ticketMap tree. * * @require The sender must be the owner of the ticket. * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. @@ -365,7 +338,6 @@ export class PLottery extends SmartContract { 'get-refund', new RefundEvent({ ticket, - round: Field(0), // #TODO remove }) ); } @@ -376,21 +348,14 @@ export class PLottery extends SmartContract { * and then sends appropriate potion of bank to ticket owner. Finally it nullify the ticket. * * @param ticket The lottery ticket for which the reward is being claimed. - * @param roundWitness The 1s level Merkle proof witness for the ticket tree. - * @param roundTicketWitness The 2nd level Merkle proof witness for the ticket tree. + * @param ticketWitness Witness of the ticket in the ticketMap tree. * @param dp The distribution proof to verify the winning numbers and ticket distribution. - * @param winningNumbers The winning numbers for the current round. - * @param resultWitness The Merkle proof witness for the result tree. - * @param bankValue The current value in the bank for this round. - * @param bankWitness The Merkle proof witness for the bank tree. * @param nullifierWitness The Merkle proof witness for the nullifier tree. * * @require The sender must be the owner of the ticket. * @require The distribution proof must be valid and match the round's ticket root and winning numbers. * @require The ticket must exist in the Merkle map as verified by the round and ticket witnesses. * @require The actions for the round must be reduced before claiming the reward. - * @require The result root must match the winning numbers for the round. - * @require The bank value must be verified and sufficient to cover the reward. * * @event get-reward Emitted when the reward is successfully claimed and transferred to the ticket owner. */ @@ -398,7 +363,7 @@ export class PLottery extends SmartContract { ticket: Ticket, ticketWitness: MerkleMap20Witness, dp: DistributionProof, - nullifierWitness: MerkleMapWitness + nullifierWitness: MerkleMap20Witness ) { // Check that owner trying to claim ticket.owner.assertEquals(this.sender.getAndRequireSignature()); @@ -451,7 +416,6 @@ export class PLottery extends SmartContract { 'get-reward', new GetRewardEvent({ ticket, - round: Field(0), // #TODO remove }) ); } @@ -459,7 +423,6 @@ export class PLottery extends SmartContract { /** * @notice Check that execution is happening within provided round * - * @param round Round to be checked * * @require globalSlotSinceGenesis to be within range of round */ @@ -474,7 +437,7 @@ export class PLottery extends SmartContract { /** * @notice Check that execution is happening after provided round * - * @param round Round to be checked + * @param amount Amounts of rounds to pass to check * * @require globalSlotSinceGenesis to be greater then last slot of given number */ @@ -490,14 +453,14 @@ export class PLottery extends SmartContract { * @notice Check validity of merkle map witness for nullifier tree and then updates tree with new value. * * @param witness Merkle map witness for nullifier tree. - * @param round Round number, that will be compared with key. + * @param key Round number, that will be compared with key. * @param curValue Value of nullifier to be checked. * @param newValue New value that should be store in tree. * * @returns key of */ public checkAndUpdateNullifier( - witness: MerkleMapWitness, + witness: MerkleMap20Witness, key: Field, curValue: Field, newValue: Field @@ -564,8 +527,3 @@ export class PLottery extends SmartContract { }; } } - -// return PLottery; -// } - -// export type PLotteryType = InstanceType>; diff --git a/src/Proofs/TicketReduceProof.ts b/src/Proofs/TicketReduceProof.ts index a2c5723..6c4fc2d 100644 --- a/src/Proofs/TicketReduceProof.ts +++ b/src/Proofs/TicketReduceProof.ts @@ -50,7 +50,6 @@ function emptyHashWithPrefix(prefix: string) { export class LotteryAction extends Struct({ ticket: Ticket, - round: Field, }) {} export const actionListAdd = (hash: Field, action: LotteryAction): Field => { diff --git a/src/Random/RandomManager.ts b/src/Random/RandomManager.ts index bdee601..157dd68 100644 --- a/src/Random/RandomManager.ts +++ b/src/Random/RandomManager.ts @@ -53,14 +53,14 @@ export class RandomManager extends SmartContract { requested: ExternalRequestEvent, }; - init() { - super.init(); - - // assert( - // Bool(false), - // 'This contract is supposed to be deployed from factory. No init call there' - // ); - } + // init() { + // super.init(); + + // // assert( + // // Bool(false), + // // 'This contract is supposed to be deployed from factory. No init call there' + // // ); + // } /** * @notice Commit hidden value. diff --git a/src/StateManager/BaseStateManager.ts b/src/StateManager/BaseStateManager.ts index 3e834e2..384054c 100644 --- a/src/StateManager/BaseStateManager.ts +++ b/src/StateManager/BaseStateManager.ts @@ -48,7 +48,7 @@ export class BaseStateManager { ticketMap: MerkleMap20; roundTickets: (Ticket | undefined)[]; // Refunded ticket will be undefined lastTicketInRound: number; - ticketNullifierMap: MerkleMap; + ticketNullifierMap: MerkleMap20; startSlot: Field; isMock: boolean; shouldUpdateState: boolean; @@ -63,7 +63,7 @@ export class BaseStateManager { this.ticketMap = new MerkleMap20(); this.lastTicketInRound = 0; this.roundTickets = []; - this.ticketNullifierMap = new MerkleMap(); + this.ticketNullifierMap = new MerkleMap20(); this.isMock = isMock; this.shouldUpdateState = shouldUpdateState; } @@ -129,7 +129,7 @@ export class BaseStateManager { ): Promise<{ ticketWitness: MerkleMap20Witness; dp: DistributionProof; - nullifierWitness: MerkleMapWitness; + nullifierWitness: MerkleMap20Witness; }> { const ticketHash = ticket.hash(); let ticketWitness; @@ -188,6 +188,7 @@ export class BaseStateManager { } if (this.shouldUpdateState) { + console.log('Update state'); this.ticketMap.set(Field(ticketId), Field(0)); this.roundTickets[ticketId] = undefined; diff --git a/src/StateManager/FactoryStateManager.ts b/src/StateManager/FactoryStateManager.ts index 59878b3..98bd873 100644 --- a/src/StateManager/FactoryStateManager.ts +++ b/src/StateManager/FactoryStateManager.ts @@ -14,12 +14,16 @@ export class FactoryManager { deploys: { [key: number]: IDeployInfo }; plotteryManagers: { [round: number]: PStateManager }; randomManagers: { [round: number]: RandomManagerManager }; + isMock: boolean; + shouldUpdateState: boolean; - constructor() { + constructor(isMock: boolean = true, shouldUpdateState: boolean = false) { this.roundsMap = new MerkleMap(); this.deploys = {}; this.plotteryManagers = {}; this.randomManagers = {}; + this.isMock = isMock; + this.shouldUpdateState = shouldUpdateState; } addDeploy(round: number, randomManager: PublicKey, plottery: PublicKey) { @@ -32,7 +36,11 @@ export class FactoryManager { const plotteryContract = new PLottery(plottery); - this.plotteryManagers[round] = new PStateManager(plotteryContract); + this.plotteryManagers[round] = new PStateManager( + plotteryContract, + this.isMock, + this.shouldUpdateState + ); this.randomManagers[round] = new RandomManagerManager(); } } diff --git a/src/StateManager/PStateManager.ts b/src/StateManager/PStateManager.ts index 15d26a1..ca763cb 100644 --- a/src/StateManager/PStateManager.ts +++ b/src/StateManager/PStateManager.ts @@ -88,7 +88,6 @@ export class PStateManager extends BaseStateManager { let input = new TicketReduceProofPublicInput({ action: new LotteryAction({ ticket: Ticket.random(this.contract.address), - round: Field(0), }), ticketWitness: this.ticketMap.getWitness(Field(0)), }); @@ -106,18 +105,9 @@ export class PStateManager extends BaseStateManager { for (let actionList of actionLists) { for (let action of actionList) { - if (+action.round != this.processedTicketData.round) { - this.processedTicketData.round = +action.round; - this.processedTicketData.ticketId = 0; - } else { - this.processedTicketData.ticketId++; - } - - console.log( - `Process ticket: <${+action.round}> <${ - this.processedTicketData.ticketId - }>` - ); + this.processedTicketData.ticketId++; + + console.log(`Process ticket: <${this.processedTicketData.ticketId}>`); input = new TicketReduceProofPublicInput({ action: action, @@ -135,9 +125,7 @@ export class PStateManager extends BaseStateManager { : await TicketReduceProgram.addTicket(input, curProof); this.addTicket(action.ticket, true); - addedTicketInfo.push({ - round: action.round, - }); + addedTicketInfo.push({}); } // Again here we do not need specific input, as it is not using here diff --git a/src/Tests/MockedContracts/MockedFactory.ts b/src/Tests/MockedContracts/MockedFactory.ts index 240abab..6f61a82 100644 --- a/src/Tests/MockedContracts/MockedFactory.ts +++ b/src/Tests/MockedContracts/MockedFactory.ts @@ -14,6 +14,7 @@ import { Permissions, method, Cache, + Provable, } from 'o1js'; import { BLOCK_PER_ROUND } from '../../constants'; import { MerkleMap20 } from '../../Structs/CustomMerkleMap'; @@ -58,6 +59,7 @@ export class MockedPlotteryFactory extends SmartContract { @state(Field) roundsRoot = State(); init() { + super.init(); this.roundsRoot.set(emptyMerkleMapRoot); } @@ -75,13 +77,15 @@ export class MockedPlotteryFactory extends SmartContract { const [newRoot] = witness.computeRootAndKeyV2(Field(1)); this.roundsRoot.set(newRoot); + const localStartSlot = startSlot.add(round.mul(BLOCK_PER_ROUND)); + // Deploy and initialize random manager { const rmUpdate = AccountUpdate.createSigned(randomManager); rmUpdate.account.verificationKey.set(mockedRandomManagerVK); rmUpdate.update.appState[0] = { isSome: Bool(true), - value: startSlot, + value: localStartSlot, }; // Update permissions @@ -96,25 +100,35 @@ export class MockedPlotteryFactory extends SmartContract { { const plotteryUpdate = AccountUpdate.createSigned(plottery); plotteryUpdate.account.verificationKey.set(PLotteryVK); - // Start slot set + + // Set random manager + const rmFields = randomManager.toFields(); + + // Random manager address plotteryUpdate.update.appState[0] = { isSome: Bool(true), - value: startSlot, + value: rmFields[0], }; - // Set random manager + plotteryUpdate.update.appState[1] = { isSome: Bool(true), - value: randomManager.x, // ? + value: rmFields[1], }; - // Set ticket ticketRoot + // Start slot set plotteryUpdate.update.appState[2] = { + isSome: Bool(true), + value: localStartSlot, + }; + + // Set ticket ticketRoot + plotteryUpdate.update.appState[3] = { isSome: Bool(true), value: new MerkleMap20().getRoot(), }; // Set ticket nullifier - plotteryUpdate.update.appState[3] = { + plotteryUpdate.update.appState[4] = { isSome: Bool(true), value: new MerkleMap20().getRoot(), }; diff --git a/src/Tests/PLottery.test.ts b/src/Tests/PLottery.test.ts index 0a543a1..cd0765b 100644 --- a/src/Tests/PLottery.test.ts +++ b/src/Tests/PLottery.test.ts @@ -1,7 +1,9 @@ import { AccountUpdate, + Bool, Cache, Field, + MerkleMap, Mina, Poseidon, PrivateKey, @@ -9,7 +11,7 @@ import { UInt32, UInt64, } from 'o1js'; -import { PLotteryType, generateNumbersSeed, getPLottery } from '../PLottery'; +import { generateNumbersSeed, PLottery } from '../PLottery'; import { Ticket } from '../Structs/Ticket'; import { NumberPacked, convertToUInt64 } from '../util'; import { @@ -23,12 +25,12 @@ import { dummyBase64Proof } from 'o1js/dist/node/lib/proof-system/zkprogram'; import { Pickles } from 'o1js/dist/node/snarky'; import { PStateManager } from '../StateManager/PStateManager'; import { TicketReduceProgram } from '../Proofs/TicketReduceProof'; -import { - CommitValue, - MockedRandomManagerType, - getMockedRandomManager, -} from '../Random/RandomManager'; +import { CommitValue, RandomManager } from '../Random/RandomManager'; import { RandomManagerManager } from '../StateManager/RandomManagerManager'; +import { MockedRandomManager } from './MockedContracts/MockedRandomManager'; +import { FactoryManager } from '../StateManager/FactoryStateManager'; +import { MerkleMap20 } from '../Structs/CustomMerkleMap'; +import { MockedPlotteryFactory } from './MockedContracts/MockedFactory'; export async function mockProof( publicOutput: O, @@ -65,6 +67,8 @@ const testWinningCombination = generateNumbersSeed( Poseidon.hash([testCommitValue.value, testVRFValue]) ); +const ROUNDS = 10; + let proofsEnabled = false; describe('Add', () => { @@ -74,18 +78,20 @@ describe('Add', () => { restAccs: Mina.TestPublicKey[], users: Mina.TestPublicKey[], senderKey: PrivateKey, - randomManagerAddress: PublicKey, - randomManagerPrivateKey: PrivateKey, - plotteryAddress: PublicKey, - plotteryPrivateKey: PrivateKey, - lottery: PLotteryType, - randomManager: MockedRandomManagerType, - rmStateManager: RandomManagerManager, - state: PStateManager, + factoryPrivateKey: PrivateKey, + factoryAddress: PublicKey, + factory: MockedPlotteryFactory, + factoryManager: FactoryManager, + plotteries: { [round: number]: PLottery }, + randomManagers: { [round: number]: MockedRandomManager }, checkConsistency: () => void, mineNBlocks: (n: number) => void, commitValue: (round: number) => Promise, - produceResultInRM: (round: number) => Promise; + produceResultInRM: (round: number) => Promise, + deployRound: (round: number) => Promise<{ + plotteryContract: PLottery; + randomManagerContract: MockedRandomManager; + }>; beforeAll(async () => { if (proofsEnabled) { console.log(`Compiling distribution program proof`); @@ -108,101 +114,142 @@ describe('Add', () => { users = restAccs.slice(0, 7); deployerKey = deployerAccount.key; senderKey = senderAccount.key; - plotteryPrivateKey = PrivateKey.random(); - plotteryAddress = plotteryPrivateKey.toPublicKey(); - randomManagerPrivateKey = PrivateKey.random(); - randomManagerAddress = randomManagerPrivateKey.toPublicKey(); - let MockedRandomManager = getMockedRandomManager(deployerAccount); - randomManager = new MockedRandomManager(randomManagerAddress); - let PLottery = getPLottery( - randomManagerAddress, - deployerAccount, - PublicKey.empty() - ); + factoryPrivateKey = PrivateKey.random(); + factoryAddress = factoryPrivateKey.toPublicKey(); - lottery = new PLottery(plotteryAddress); - state = new PStateManager( - lottery, - Local.getNetworkState().blockchainLength.value, - !proofsEnabled, - true - ); + factory = new MockedPlotteryFactory(factoryAddress); + plotteries = {}; + randomManagers = {}; - rmStateManager = new RandomManagerManager(); + // rmStateManager = new RandomManagerManager(); + factoryManager = new FactoryManager(true, true); mineNBlocks = (n: number) => { let curAmount = Local.getNetworkState().globalSlotSinceGenesis; Local.setGlobalSlot(curAmount.add(n)); }; checkConsistency = () => { - expect(lottery.ticketRoot.get()).toEqual(state.ticketMap.getRoot()); - expect(lottery.ticketNullifier.get()).toEqual( - state.ticketNullifierMap.getRoot() - ); - expect(lottery.bankRoot.get()).toEqual(state.bankMap.getRoot()); - expect(lottery.roundResultRoot.get()).toEqual( - state.roundResultMap.getRoot() - ); + // expect(lottery.ticketRoot.get()).toEqual(state.ticketMap.getRoot()); + // expect(lottery.ticketNullifier.get()).toEqual( + // state.ticketNullifierMap.getRoot() + // ); + // expect(lottery.bankRoot.get()).toEqual(state.bankMap.getRoot()); + // expect(lottery.roundResultRoot.get()).toEqual( + // state.roundResultMap.getRoot() + // ); }; commitValue = async (round: number) => { - const commitWV = rmStateManager.getCommitWitness(round); + let randomManager = randomManagers[round]; + let rmStateManager = factoryManager.randomManagers[round]; let tx = Mina.transaction(deployerAccount, async () => { - randomManager.commit(testCommitValue, commitWV.witness); + randomManager.commitValue(testCommitValue); }); await tx.prove(); await tx.sign([deployerKey]).send(); - rmStateManager.addCommit(round, testCommitValue); + rmStateManager.addCommit(testCommitValue); }; produceResultInRM = async (round: number) => { + let randomManager = randomManagers[round]; + let tx = Mina.transaction(deployerAccount, async () => { randomManager.mockReceiveZkonResponse(testVRFValue); }); await tx.prove(); await tx.sign([deployerKey]).send(); - const commitWV = rmStateManager.getCommitWitness(round); - const resultWV = rmStateManager.getResultWitness(round); let tx2 = Mina.transaction(deployerAccount, async () => { - randomManager.reveal( - testCommitValue, - commitWV.witness, - resultWV.witness - ); + randomManager.reveal(testCommitValue); }); await tx2.prove(); await tx2.sign([deployerKey]).send(); - rmStateManager.addResultValue( + }; + + deployRound = async (round: number) => { + const roundWitness = factoryManager.roundsMap.getWitness(Field(round)); + const randomManagerKeypair = PrivateKey.randomKeypair(); + const plotteryKeypair = PrivateKey.randomKeypair(); + + const tx = Mina.transaction(deployerAccount, async () => { + AccountUpdate.fundNewAccount(deployerAccount); + AccountUpdate.fundNewAccount(deployerAccount); + await factory.deployRound( + roundWitness, + randomManagerKeypair.publicKey, + plotteryKeypair.publicKey + ); + }); + await tx.prove(); + await tx + .sign([ + deployerKey, + randomManagerKeypair.privateKey, + plotteryKeypair.privateKey, + ]) + .send(); + + factoryManager.addDeploy( round, - Poseidon.hash([testCommitValue.value, testVRFValue]) + randomManagerKeypair.publicKey, + plotteryKeypair.publicKey ); + + const plotteryContract = new PLottery(plotteryKeypair.publicKey); + const randomManagerContract = new MockedRandomManager( + randomManagerKeypair.publicKey + ); + + return { + plotteryContract, + randomManagerContract, + }; }; }); async function localDeploy() { const txn = await Mina.transaction(deployerAccount, async () => { AccountUpdate.fundNewAccount(deployerAccount); - await lottery.deploy(); - - AccountUpdate.fundNewAccount(deployerAccount); - await randomManager.deploy(); + await factory.deploy(); }); await txn.prove(); // this tx needs .sign(), because `deploy()` adds an account update that requires signature authorization - await txn - .sign([deployerKey, plotteryPrivateKey, randomManagerPrivateKey]) - .send(); + await txn.sign([deployerKey, factoryPrivateKey]).send(); - const initTx = Mina.transaction(deployerAccount, async () => { - await randomManager.setStartSlot(lottery.startBlock.get()); - }); - await initTx.prove(); - await initTx.sign([deployerKey]).send(); + for (let i = 0; i < ROUNDS; i++) { + const { plotteryContract, randomManagerContract } = await deployRound(i); + plotteries[i] = plotteryContract; + randomManagers[i] = randomManagerContract; + } } + + it('check plottery initial values', async () => { + await localDeploy(); + + for (let i = 0; i < ROUNDS; i++) { + let plottery = plotteries[i]; + + expect(plottery.randomManager.get()).toEqual(randomManagers[i].address); + expect(plottery.startSlot.get()).toEqual( + UInt32.from(BLOCK_PER_ROUND * i) + ); + expect(plottery.ticketRoot.get()).toEqual(new MerkleMap20().getRoot()); + expect(plottery.ticketNullifier.get()).toEqual( + new MerkleMap20().getRoot() + ); + expect(plottery.bank.get()).toEqual(Field(0)); + expect(plottery.result.get()).toEqual(Field(0)); + expect(plottery.reduced.get()).toEqual(Bool(false)); + } + }); + it('one user case', async () => { await localDeploy(); let curRound = 0; const balanceBefore = Mina.getBalance(senderAccount); // Buy ticket const ticket = Ticket.from(testWinningCombination, senderAccount, 1); + + let state = factoryManager.plotteryManagers[curRound]; + let lottery = plotteries[curRound]; + let tx = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(ticket, Field.from(curRound)); + await lottery.buyTicket(ticket); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -213,17 +260,7 @@ describe('Add', () => { await commitValue(curRound); // Wait next round mineNBlocks(BLOCK_PER_ROUND); - // Buy dummy ticket in next round, so reducer works as expected - state.syncWithCurBlock( - +Mina.activeInstance.getNetworkState().globalSlotSinceGenesis - ); - let dummy_ticket = Ticket.random(senderAccount); - dummy_ticket.amount = UInt64.zero; - let tx_1 = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(dummy_ticket, Field.from(curRound + 1)); - }); - await tx_1.prove(); - await tx_1.sign([senderKey]).send(); + // Reduce tickets let reduceProof = await state.reduceTickets(); let tx2_1 = await Mina.transaction(senderAccount, async () => { @@ -231,24 +268,16 @@ describe('Add', () => { }); await tx2_1.prove(); await tx2_1.sign([senderKey]).send(); + + expect(lottery.reduced.get()).toEqual(Bool(true)); + checkConsistency(); // Produce random value await produceResultInRM(curRound); // Produce result - const resultWV = rmStateManager.getResultWitness(curRound); - const { resultWitness, bankValue, bankWitness } = state.updateResult( - curRound, - NumberPacked.pack(generateNumbersSeed(resultWV.value)) - ); let tx2 = await Mina.transaction(senderAccount, async () => { - await lottery.produceResult( - resultWitness, - bankValue, - bankWitness, - resultWV.witness, - resultWV.value - ); + await lottery.produceResult(); }); await tx2.prove(); await tx2.sign([senderKey]).send(); @@ -262,13 +291,8 @@ describe('Add', () => { Mina.transaction(restAccs[0], async () => { await lottery.getReward( ticket, - rp.roundWitness, - rp.roundTicketWitness, + rp.ticketWitness, rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, rp.nullifierWitness ); }) @@ -280,13 +304,8 @@ describe('Add', () => { Mina.transaction(restAccs[0], async () => { await lottery.getReward( faultTicket, - rp.roundWitness, - rp.roundTicketWitness, + rp.ticketWitness, rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, rp.nullifierWitness ); }) @@ -297,13 +316,8 @@ describe('Add', () => { let tx3 = await Mina.transaction(senderAccount, async () => { await lottery.getReward( ticket, - rp.roundWitness, - rp.roundTicketWitness, + rp.ticketWitness, rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, rp.nullifierWitness ); }); @@ -311,14 +325,17 @@ describe('Add', () => { await tx3.sign([senderKey]).send(); checkConsistency(); }); + it('Refund check', async () => { await localDeploy(); let curRound = 0; + let lottery = plotteries[curRound]; + let state = factoryManager.plotteryManagers[curRound]; const balanceBefore = Mina.getBalance(senderAccount); // Buy ticket const ticket = Ticket.from(testWinningCombination, senderAccount, 1); let tx = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(ticket, Field.from(curRound)); + await lottery.buyTicket(ticket); }); await tx.prove(); await tx.sign([senderKey]).send(); @@ -327,7 +344,7 @@ describe('Add', () => { checkConsistency(); // Buy second ticket let tx1_1 = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(ticket, Field.from(curRound)); + await lottery.buyTicket(ticket); }); await tx1_1.prove(); await tx1_1.sign([senderKey]).send(); @@ -335,44 +352,26 @@ describe('Add', () => { await commitValue(curRound); // Wait 3 more rounds mineNBlocks(3 * BLOCK_PER_ROUND + 1); - // Buy dummy ticket in next round, so reducer works as expected - state.syncWithCurBlock( - +Mina.activeInstance.getNetworkState().globalSlotSinceGenesis - ); // Reduce tickets - // Buy dummy ticket - let dummy_ticket = Ticket.random(senderAccount); - dummy_ticket.amount = UInt64.zero; - let tx_1 = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(dummy_ticket, Field.from(3)); - }); - await tx_1.prove(); - await tx_1.sign([senderKey]).send(); let reduceProof = await state.reduceTickets(); let tx2_1 = await Mina.transaction(senderAccount, async () => { await lottery.reduceTickets(reduceProof); }); await tx2_1.prove(); await tx2_1.sign([senderKey]).send(); + + expect(lottery.reduced.get()).toEqual(Bool(true)); + checkConsistency(); // Get refund - let { - roundWitness, - roundTicketWitness, - resultWitness: resultWitness1, - bankValue: bankValue1, - bankWitness: bankWitness1, - } = await state.getRefund(0, ticket); + + console.log(`Before: ${lottery.ticketRoot.get().toString()}`); + console.log(state.ticketMap.getRoot().toString()); + + let { ticketWitness } = await state.getRefund(0, ticket); const balanceBefore2 = Mina.getBalance(senderAccount); let tx3 = await Mina.transaction(senderAccount, async () => { - await lottery.refund( - ticket, - roundWitness, - roundTicketWitness, - resultWitness1, - bankValue1, - bankWitness1 - ); + await lottery.refund(ticket, ticketWitness); }); await tx3.prove(); await tx3.sign([senderKey]).send(); @@ -382,20 +381,12 @@ describe('Add', () => { // Produce random value await produceResultInRM(curRound); + console.log(`After: ${lottery.ticketRoot.get().toString()}`); + console.log(state.ticketMap.getRoot().toString()); + // Produce result - const resultWV = rmStateManager.getResultWitness(curRound); - const { resultWitness, bankValue, bankWitness } = state.updateResult( - curRound, - NumberPacked.pack(generateNumbersSeed(resultWV.value)) - ); let tx4 = await Mina.transaction(senderAccount, async () => { - await lottery.produceResult( - resultWitness, - bankValue, - bankWitness, - resultWV.witness, - resultWV.value - ); + await lottery.produceResult(); }); await tx4.prove(); await tx4.sign([senderKey]).send(); @@ -406,13 +397,8 @@ describe('Add', () => { let tx5 = await Mina.transaction(senderAccount, async () => { await lottery.getReward( ticket, - rp.roundWitness, - rp.roundTicketWitness, + rp.ticketWitness, rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, rp.nullifierWitness ); }); @@ -420,19 +406,22 @@ describe('Add', () => { await tx5.sign([senderKey]).send(); checkConsistency(); const balanceAfter3 = Mina.getBalance(senderAccount); - console.log(`Bank: ${state.bankMap.get(Field(0)).toString()}`); + console.log(`Bank: ${lottery.bank.get().toString()}`); console.log(); expect(balanceAfter3.sub(balanceBefore3)).toEqual( TICKET_PRICE.mul(97).div(100) ); }); + it('Multiple round test', async () => { await localDeploy(); const amountOfRounds = 5; const amountOfTickets = 10; for (let round = 0; round < amountOfRounds; round++) { + let lottery = plotteries[round]; + let state = factoryManager.plotteryManagers[round]; + console.log(`Process: ${round} round`); - // Generate tickets let tickets = []; for (let j = 0; j < amountOfTickets; j++) { let ticket = Ticket.random(users[j % users.length]); @@ -446,7 +435,7 @@ describe('Add', () => { let ticket = tickets[j]; const balanceBefore = Mina.getBalance(ticket.owner); let tx = await Mina.transaction(ticket.owner, async () => { - await lottery.buyTicket(ticket.ticket, Field.from(round)); + await lottery.buyTicket(ticket.ticket); }); await tx.prove(); await tx.sign([ticket.owner.key]).send(); @@ -459,46 +448,27 @@ describe('Add', () => { // Wait for the end of round mineNBlocks(BLOCK_PER_ROUND); // Reduce tickets - // Buy dummy ticket in next round, so reducer works as expected - state.syncWithCurBlock( - +Mina.activeInstance.getNetworkState().globalSlotSinceGenesis - ); - let dummy_ticket = Ticket.random(senderAccount); - dummy_ticket.amount = UInt64.zero; - let tx_1 = await Mina.transaction(senderAccount, async () => { - await lottery.buyTicket(dummy_ticket, Field.from(round + 1)); - }); - await tx_1.prove(); - await tx_1.sign([senderKey]).send(); let reduceProof = await state.reduceTickets(); let tx2_1 = await Mina.transaction(senderAccount, async () => { await lottery.reduceTickets(reduceProof); }); await tx2_1.prove(); await tx2_1.sign([senderKey]).send(); + + expect(lottery.reduced.get()).toEqual(Bool(true)); + checkConsistency(); // Produce random value await produceResultInRM(round); // Produce result - const resultWV = rmStateManager.getResultWitness(round); - const { resultWitness, bankValue, bankWitness } = state.updateResult( - round, - NumberPacked.pack(generateNumbersSeed(resultWV.value)) - ); let tx2 = await Mina.transaction(senderAccount, async () => { - await lottery.produceResult( - resultWitness, - bankValue, - bankWitness, - resultWV.witness, - resultWV.value - ); + await lottery.produceResult(); }); await tx2.prove(); await tx2.sign([senderKey]).send(); checkConsistency(); - const bank = convertToUInt64(state.bankMap.get(Field(round))); + const bank = convertToUInt64(lottery.bank.get()); // Get rewards for (let j = 0; j < amountOfTickets; j++) { const ticketInfo = tickets[j]; @@ -511,13 +481,8 @@ describe('Add', () => { let tx3 = await Mina.transaction(ticketInfo.owner, async () => { await lottery.getReward( ticket, - rp.roundWitness, - rp.roundTicketWitness, + rp.ticketWitness, rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, rp.nullifierWitness ); }); @@ -531,10 +496,6 @@ describe('Add', () => { bank.mul(score).div(rp.dp.publicOutput.total) ); } - // Sync state round - state.syncWithCurBlock( - +Mina.activeInstance.getNetworkState().globalSlotSinceGenesis - ); } }); }); From cd6734411415bca02ce1b825c1dd01f067996c4c Mon Sep 17 00:00:00 2001 From: aii23 Date: Wed, 9 Oct 2024 13:34:14 +0700 Subject: [PATCH 4/6] commented some scripts --- scripts/analyze.ts | 42 +- scripts/call_zkon.ts | 44 +- scripts/commit_value.ts | 66 +- scripts/deploy.ts | 362 +++++----- scripts/deploy_1rnd_pottery.ts | 60 +- scripts/deploy_factory.ts | 40 +- scripts/generate_cache.ts | 78 +-- scripts/global_test/deploy_lightnet.ts | 120 ++-- scripts/global_test/events.ts | 838 +++++++++++------------ scripts/global_test/global_test.ts | 44 +- scripts/hash_proof.ts | 54 +- scripts/pbuy_ticket.ts | 216 +++--- scripts/prepare_cache.ts | 32 +- scripts/proof.ts | 248 +++---- scripts/publish_request.ts | 40 +- scripts/reveal_value.ts | 80 +-- src/Factory.ts | 14 +- src/StateManager/FactoryStateManager.ts | 6 +- src/StateManager/RandomManagerManager.ts | 2 +- 19 files changed, 1219 insertions(+), 1167 deletions(-) diff --git a/scripts/analyze.ts b/scripts/analyze.ts index ce4f060..4173af2 100644 --- a/scripts/analyze.ts +++ b/scripts/analyze.ts @@ -1,29 +1,29 @@ -import { getPLottery } from '../src/PLottery.js'; -import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -import { writeFileSync } from 'fs'; -import { PublicKey } from 'o1js'; -import { findPlottery } from './utils.js'; +// import { getPLottery } from '../src/PLottery.js'; +// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +// import { writeFileSync } from 'fs'; +// import { PublicKey } from 'o1js'; +// import { findPlottery } from './utils.js'; -let { PLottery } = findPlottery(); +// let { PLottery } = findPlottery(); -const lotteryResult = await PLottery.analyzeMethods(); -const distributionResult = await DistributionProgram.analyzeMethods(); +// const lotteryResult = await PLottery.analyzeMethods(); +// const distributionResult = await DistributionProgram.analyzeMethods(); -if (!PLottery._methods) { - console.log("Can't find methods for Lottery"); - throw new Error("Can't find methods for Lottery"); -} +// if (!PLottery._methods) { +// console.log("Can't find methods for Lottery"); +// throw new Error("Can't find methods for Lottery"); +// } -let result: { [name: string]: number } = {}; +// let result: { [name: string]: number } = {}; -for (const method of PLottery._methods) { - result[`Lottery_${method.methodName}`] = - lotteryResult[method.methodName].rows; -} +// for (const method of PLottery._methods) { +// result[`Lottery_${method.methodName}`] = +// lotteryResult[method.methodName].rows; +// } -result[`DistributionProof_init`] = distributionResult.init.rows; -result[`DistibutionProof_addTicket`] = distributionResult.addTicket.rows; +// result[`DistributionProof_init`] = distributionResult.init.rows; +// result[`DistibutionProof_addTicket`] = distributionResult.addTicket.rows; -console.log(result); +// console.log(result); -writeFileSync('analyze_result.json', JSON.stringify(result, null, 2)); +// writeFileSync('analyze_result.json', JSON.stringify(result, null, 2)); diff --git a/scripts/call_zkon.ts b/scripts/call_zkon.ts index afdc361..a7b89b2 100644 --- a/scripts/call_zkon.ts +++ b/scripts/call_zkon.ts @@ -1,29 +1,29 @@ -import { Field, Mina, Poseidon } from 'o1js'; -import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; -import { - compileRandomManager, - configDefaultInstance, - findPlottery, - findRandomManager, - getDeployer, - getRMStoreManager, - storeRMStoreManager, -} from './utils'; -import { CommitValue } from '../src/Random/RandomManager'; +// import { Field, Mina, Poseidon } from 'o1js'; +// import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; +// import { +// compileRandomManager, +// configDefaultInstance, +// findPlottery, +// findRandomManager, +// getDeployer, +// getRMStoreManager, +// storeRMStoreManager, +// } from './utils'; +// import { CommitValue } from '../src/Random/RandomManager'; -configDefaultInstance(); +// configDefaultInstance(); -let deploy_epoch = process.argv[2] ? process.argv[2] : 'current'; +// let deploy_epoch = process.argv[2] ? process.argv[2] : 'current'; -let { deployer, deployerKey } = getDeployer(); +// let { deployer, deployerKey } = getDeployer(); -let { randomManager } = findRandomManager(deploy_epoch); +// let { randomManager } = findRandomManager(deploy_epoch); -await compileRandomManager(deploy_epoch); +// await compileRandomManager(deploy_epoch); -let tx = await Mina.transaction(deployer, async () => { - await randomManager.callZkon(); -}); +// let tx = await Mina.transaction(deployer, async () => { +// await randomManager.callZkon(); +// }); -await tx.prove(); -await tx.sign([deployerKey]).send(); +// await tx.prove(); +// await tx.sign([deployerKey]).send(); diff --git a/scripts/commit_value.ts b/scripts/commit_value.ts index fd13c98..4d47d19 100644 --- a/scripts/commit_value.ts +++ b/scripts/commit_value.ts @@ -1,47 +1,47 @@ -import { Field, Mina } from 'o1js'; -import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; -import { - compileRandomManager, - configDefaultInstance, - findPlottery, - findRandomManager, - getDeployer, - getRMStoreManager, - storeRMStoreManager, -} from './utils'; -import { CommitValue } from '../src/Random/RandomManager'; +// import { Field, Mina } from 'o1js'; +// import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; +// import { +// compileRandomManager, +// configDefaultInstance, +// findPlottery, +// findRandomManager, +// getDeployer, +// getRMStoreManager, +// storeRMStoreManager, +// } from './utils'; +// import { CommitValue } from '../src/Random/RandomManager'; -configDefaultInstance(); +// configDefaultInstance(); -let round = process.argv[2]; +// let round = process.argv[2]; -if (!round) { - throw Error(`You should specify round`); -} +// if (!round) { +// throw Error(`You should specify round`); +// } -let deploy_epoch = process.argv[3] ? process.argv[3] : 'current'; +// let deploy_epoch = process.argv[3] ? process.argv[3] : 'current'; -let { deployer, deployerKey } = getDeployer(); +// let { deployer, deployerKey } = getDeployer(); -let { randomManager } = findRandomManager(deploy_epoch); +// let { randomManager } = findRandomManager(deploy_epoch); -await compileRandomManager(deploy_epoch); +// await compileRandomManager(deploy_epoch); -let rmStoreManager: RandomManagerManager = getRMStoreManager(deploy_epoch); +// let rmStoreManager: RandomManagerManager = getRMStoreManager(deploy_epoch); -let value = Field.random(); -let salt = Field.random(); -let commitValue = new CommitValue({ value, salt }); +// let value = Field.random(); +// let salt = Field.random(); +// let commitValue = new CommitValue({ value, salt }); -const { witness: commitRoundWitness } = rmStoreManager.getCommitWitness(+round); +// const { witness: commitRoundWitness } = rmStoreManager.getCommitWitness(+round); -let tx = await Mina.transaction(deployer, async () => { - await randomManager.commit(commitValue, commitRoundWitness); -}); +// let tx = await Mina.transaction(deployer, async () => { +// await randomManager.commit(commitValue, commitRoundWitness); +// }); -await tx.prove(); -await tx.sign([deployerKey]).send(); +// await tx.prove(); +// await tx.sign([deployerKey]).send(); -rmStoreManager.addCommit(+round, commitValue); +// rmStoreManager.addCommit(+round, commitValue); -storeRMStoreManager(rmStoreManager, deploy_epoch); +// storeRMStoreManager(rmStoreManager, deploy_epoch); diff --git a/scripts/deploy.ts b/scripts/deploy.ts index 74409b9..f751058 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -1,181 +1,181 @@ -import dotenv from 'dotenv'; -dotenv.config(); -import { Mina, PrivateKey, fetchAccount, AccountUpdate, PublicKey } from 'o1js'; -import { ZkonZkProgram, ZkonRequestCoordinator } from 'zkon-zkapp'; -import { getRandomManager } from '../src/Random/RandomManager.js'; -import { getPLottery } from '../src/PLottery.js'; - -import * as fs from 'fs'; -import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; -import { configDefaultInstance } from './utils.js'; - -const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); - -// Network configuration -const { transactionFee } = configDefaultInstance(); -// const transactionFee = 500_000_000; -// const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; -// const network = Mina.Network({ -// mina: useCustomLocalNetwork -// ? 'http://localhost:8080/graphql' -// : 'https://api.minascan.io/node/devnet/v1/graphql', -// lightnetAccountManager: 'http://localhost:8181', -// archive: useCustomLocalNetwork -// ? 'http://localhost:8282' -// : 'https://api.minascan.io/archive/devnet/v1/graphql', -// }); -// Mina.setActiveInstance(network); - -let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); -let deployer = deployerKey.toPublicKey(); - -let randomManagerOwner = PublicKey.fromBase58( - process.env.RANDOM_MANAGER_OWNER_ADDRESS! -); - -console.log(`Fetching the fee payer account information.`); -const accountDetails = (await fetchAccount({ publicKey: deployer })).account; -console.log( - `Using the fee payer account ${deployer.toBase58()} with nonce: ${ - accountDetails?.nonce - } and balance: ${accountDetails?.balance}.` -); - -console.log('Compiling proofs'); -await ZkonZkProgram.compile(); -await ZkonRequestCoordinator.compile(); - -const randomManagerPrivateKey = PrivateKey.random(); -const randomManagerAddress = randomManagerPrivateKey.toPublicKey(); - -const lotteryPrivateKey = PrivateKey.random(); -const lotteryAddress = lotteryPrivateKey.toPublicKey(); - -// Deploy random manager - -console.log( - `Deploying random manager on address: ${randomManagerAddress.toBase58()}` -); -let RandomManager = getRandomManager(randomManagerOwner); -let randomManager = new RandomManager(randomManagerAddress); -await RandomManager.compile(); - -let rmDeployTx = await Mina.transaction( - { sender: deployer, fee: transactionFee }, - async () => { - AccountUpdate.fundNewAccount(deployer); - await randomManager.deploy(); - } -); - -await rmDeployTx.prove(); -let rmDeployTxStatus = await rmDeployTx - .sign([deployerKey, randomManagerPrivateKey]) - .send(); - -console.log( - `Transaction for random manger deploy sent. Hash: ${rmDeployTxStatus.hash}` -); - -console.log(`Wait 15 minutes for transaction to complete`); -await wait(15 * 60 * 1000); - -// Deploy lottery -console.log(`Deploying lottery on address: ${lotteryAddress.toBase58()}`); -let Lottery = getPLottery(randomManagerAddress, randomManagerAddress); -let lottery = new Lottery(lotteryAddress); -await DistributionProgram.compile(); -await TicketReduceProgram.compile(); -await Lottery.compile(); - -let lotteryDeployTx = await Mina.transaction( - { sender: deployer, fee: transactionFee }, - async () => { - AccountUpdate.fundNewAccount(deployer); - await lottery.deploy(); - } -); - -await lotteryDeployTx.prove(); -let lotteryDeployTxStatus = await lotteryDeployTx - .sign([deployerKey, lotteryPrivateKey]) - .send(); - -console.log( - `Transaction for lottery deploy sent. Hash: ${lotteryDeployTxStatus.hash}` -); - -console.log('Writing addreses to files'); -// Store keys -let deployParams: { lastDeploy: number }; - -if (!fs.existsSync('./deploy')) { - fs.mkdirSync('./deploy'); -} - -if (fs.existsSync('./deploy/params.json')) { - let deployParamsBuffer = fs.readFileSync('./deploy/params.json'); - deployParams = JSON.parse(deployParamsBuffer.toString()); -} else { - deployParams = { - lastDeploy: 0, - }; -} - -deployParams.lastDeploy++; - -// Store private keys -let deployedKeys = { - randomManagerPrivateKey: randomManagerPrivateKey.toBase58(), - randomManagerAddress: randomManagerAddress.toBase58(), - lotteryPrivateKey: lotteryPrivateKey.toBase58(), - lotteryAddress: lotteryAddress.toBase58(), -}; - -if (!fs.existsSync('./keys/auto')) { - fs.mkdirSync('./keys/auto', { recursive: true }); -} - -fs.writeFileSync( - `./keys/auto/${deployParams.lastDeploy}.json`, - JSON.stringify(deployedKeys, null, 2), - { flag: 'wx' } -); - -// Store adresses -let addresses = { - randomManagerAddress: randomManagerAddress.toBase58(), - lotteryAddress: lotteryAddress.toBase58(), - randomManagerOwner: randomManagerOwner.toBase58(), -}; - -if (!fs.existsSync('./deploy/addresses')) { - fs.mkdirSync('./deploy/addresses', { recursive: true }); -} - -fs.writeFileSync( - `./deploy/addresses/${deployParams.lastDeploy}.json`, - JSON.stringify(addresses, null, 2), - { flag: 'wx' } -); - -fs.writeFileSync( - `./deploy/addresses/current.json`, - JSON.stringify(addresses, null, 2) -); - -// Update deploy params -fs.writeFileSync(`./deploy/params.json`, JSON.stringify(deployParams, null, 2)); - -console.log('Done'); -let deployParamsT: { lastDeploy: number }; - -if (fs.existsSync('foo.txt')) { - let deployParamsBufferT = fs.readFileSync('./deploy/params.json'); - deployParamsT = JSON.parse(deployParamsBufferT.toString()); -} else { - deployParamsT = { - lastDeploy: 0, - }; -} +// import dotenv from 'dotenv'; +// dotenv.config(); +// import { Mina, PrivateKey, fetchAccount, AccountUpdate, PublicKey } from 'o1js'; +// import { ZkonZkProgram, ZkonRequestCoordinator } from 'zkon-zkapp'; +// import { getRandomManager } from '../src/Random/RandomManager.js'; +// import { getPLottery } from '../src/PLottery.js'; + +// import * as fs from 'fs'; +// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +// import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; +// import { configDefaultInstance } from './utils.js'; + +// const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); + +// // Network configuration +// const { transactionFee } = configDefaultInstance(); +// // const transactionFee = 500_000_000; +// // const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +// // const network = Mina.Network({ +// // mina: useCustomLocalNetwork +// // ? 'http://localhost:8080/graphql' +// // : 'https://api.minascan.io/node/devnet/v1/graphql', +// // lightnetAccountManager: 'http://localhost:8181', +// // archive: useCustomLocalNetwork +// // ? 'http://localhost:8282' +// // : 'https://api.minascan.io/archive/devnet/v1/graphql', +// // }); +// // Mina.setActiveInstance(network); + +// let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); +// let deployer = deployerKey.toPublicKey(); + +// let randomManagerOwner = PublicKey.fromBase58( +// process.env.RANDOM_MANAGER_OWNER_ADDRESS! +// ); + +// console.log(`Fetching the fee payer account information.`); +// const accountDetails = (await fetchAccount({ publicKey: deployer })).account; +// console.log( +// `Using the fee payer account ${deployer.toBase58()} with nonce: ${ +// accountDetails?.nonce +// } and balance: ${accountDetails?.balance}.` +// ); + +// console.log('Compiling proofs'); +// await ZkonZkProgram.compile(); +// await ZkonRequestCoordinator.compile(); + +// const randomManagerPrivateKey = PrivateKey.random(); +// const randomManagerAddress = randomManagerPrivateKey.toPublicKey(); + +// const lotteryPrivateKey = PrivateKey.random(); +// const lotteryAddress = lotteryPrivateKey.toPublicKey(); + +// // Deploy random manager + +// console.log( +// `Deploying random manager on address: ${randomManagerAddress.toBase58()}` +// ); +// let RandomManager = getRandomManager(randomManagerOwner); +// let randomManager = new RandomManager(randomManagerAddress); +// await RandomManager.compile(); + +// let rmDeployTx = await Mina.transaction( +// { sender: deployer, fee: transactionFee }, +// async () => { +// AccountUpdate.fundNewAccount(deployer); +// await randomManager.deploy(); +// } +// ); + +// await rmDeployTx.prove(); +// let rmDeployTxStatus = await rmDeployTx +// .sign([deployerKey, randomManagerPrivateKey]) +// .send(); + +// console.log( +// `Transaction for random manger deploy sent. Hash: ${rmDeployTxStatus.hash}` +// ); + +// console.log(`Wait 15 minutes for transaction to complete`); +// await wait(15 * 60 * 1000); + +// // Deploy lottery +// console.log(`Deploying lottery on address: ${lotteryAddress.toBase58()}`); +// let Lottery = getPLottery(randomManagerAddress, randomManagerAddress); +// let lottery = new Lottery(lotteryAddress); +// await DistributionProgram.compile(); +// await TicketReduceProgram.compile(); +// await Lottery.compile(); + +// let lotteryDeployTx = await Mina.transaction( +// { sender: deployer, fee: transactionFee }, +// async () => { +// AccountUpdate.fundNewAccount(deployer); +// await lottery.deploy(); +// } +// ); + +// await lotteryDeployTx.prove(); +// let lotteryDeployTxStatus = await lotteryDeployTx +// .sign([deployerKey, lotteryPrivateKey]) +// .send(); + +// console.log( +// `Transaction for lottery deploy sent. Hash: ${lotteryDeployTxStatus.hash}` +// ); + +// console.log('Writing addreses to files'); +// // Store keys +// let deployParams: { lastDeploy: number }; + +// if (!fs.existsSync('./deploy')) { +// fs.mkdirSync('./deploy'); +// } + +// if (fs.existsSync('./deploy/params.json')) { +// let deployParamsBuffer = fs.readFileSync('./deploy/params.json'); +// deployParams = JSON.parse(deployParamsBuffer.toString()); +// } else { +// deployParams = { +// lastDeploy: 0, +// }; +// } + +// deployParams.lastDeploy++; + +// // Store private keys +// let deployedKeys = { +// randomManagerPrivateKey: randomManagerPrivateKey.toBase58(), +// randomManagerAddress: randomManagerAddress.toBase58(), +// lotteryPrivateKey: lotteryPrivateKey.toBase58(), +// lotteryAddress: lotteryAddress.toBase58(), +// }; + +// if (!fs.existsSync('./keys/auto')) { +// fs.mkdirSync('./keys/auto', { recursive: true }); +// } + +// fs.writeFileSync( +// `./keys/auto/${deployParams.lastDeploy}.json`, +// JSON.stringify(deployedKeys, null, 2), +// { flag: 'wx' } +// ); + +// // Store adresses +// let addresses = { +// randomManagerAddress: randomManagerAddress.toBase58(), +// lotteryAddress: lotteryAddress.toBase58(), +// randomManagerOwner: randomManagerOwner.toBase58(), +// }; + +// if (!fs.existsSync('./deploy/addresses')) { +// fs.mkdirSync('./deploy/addresses', { recursive: true }); +// } + +// fs.writeFileSync( +// `./deploy/addresses/${deployParams.lastDeploy}.json`, +// JSON.stringify(addresses, null, 2), +// { flag: 'wx' } +// ); + +// fs.writeFileSync( +// `./deploy/addresses/current.json`, +// JSON.stringify(addresses, null, 2) +// ); + +// // Update deploy params +// fs.writeFileSync(`./deploy/params.json`, JSON.stringify(deployParams, null, 2)); + +// console.log('Done'); +// let deployParamsT: { lastDeploy: number }; + +// if (fs.existsSync('foo.txt')) { +// let deployParamsBufferT = fs.readFileSync('./deploy/params.json'); +// deployParamsT = JSON.parse(deployParamsBufferT.toString()); +// } else { +// deployParamsT = { +// lastDeploy: 0, +// }; +// } diff --git a/scripts/deploy_1rnd_pottery.ts b/scripts/deploy_1rnd_pottery.ts index 1029e3a..b1cb824 100644 --- a/scripts/deploy_1rnd_pottery.ts +++ b/scripts/deploy_1rnd_pottery.ts @@ -1,14 +1,16 @@ import { Field, Mina, PrivateKey, PublicKey } from 'o1js'; -import { DeployEvent, PlotteryFactory } from '../src/Factory'; -import { FactoryManager } from '../src/StateManager/FactoryStateManager'; -import { configDefaultInstance } from './utils'; +import { DeployEvent, PlotteryFactory } from '../src/Factory.js'; +import { FactoryManager } from '../src/StateManager/FactoryStateManager.js'; +import { configDefaultInstance } from './utils.js'; import * as fs from 'fs'; -configDefaultInstance(); +let { transactionFee } = configDefaultInstance(); let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); let deployer = deployerKey.toPublicKey(); +console.log(`Using deployer ${deployer.toBase58()}`); + let from = process.argv[2]; let to = process.argv[3]; @@ -17,13 +19,23 @@ if (!from || !to) { throw Error(`You should provide from round and to round`); } +console.log(`Compiling PlotteryFactory`); + +const networkId = Mina.activeInstance.getNetworkId().toString(); + let { verificationKey } = await PlotteryFactory.compile(); const factoryManager = new FactoryManager(); let factoryAddress: PublicKey; -const factoryDataPath = `./deployV2/${verificationKey.hash.toString()}/factory.json`; +if ( + !fs.existsSync(`./deployV2/${networkId}/${verificationKey.hash.toString()}`) +) { + throw Error(`No factory deployment found. Deploy it first`); +} + +const factoryDataPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/factory.json`; if (fs.existsSync(factoryDataPath)) { let factoryData = fs.readFileSync(factoryDataPath); factoryAddress = PublicKey.fromBase58( @@ -39,7 +51,7 @@ let factoryEvents = await factory.fetchEvents(); let deployments; -const deploymentsPath = `./deployV2/${verificationKey.hash.toString()}/deployments.json`; +const deploymentsPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/deployments.json`; if (fs.existsSync(deploymentsPath)) { let deploymentsBuffer = fs.readFileSync(deploymentsPath); @@ -52,6 +64,8 @@ if (fs.existsSync(deploymentsPath)) { for (const event of factoryEvents) { let deployEvent = event.event.data as any; + console.log('event'); + console.log(deployEvent); factoryManager.addDeploy( +deployEvent.round, deployEvent.randomManager, @@ -60,6 +74,10 @@ for (const event of factoryEvents) { } for (let round = +from; round <= +to; round++) { + if (factoryManager.roundsMap.get(Field(round)).greaterThan(0).toBoolean()) { + console.log(`Plottery for round ${round} have been deployed`); + continue; + } let witness = factoryManager.roundsMap.getWitness(Field(round)); let plotteryPrivateKey = PrivateKey.random(); @@ -68,15 +86,31 @@ for (let round = +from; round <= +to; round++) { let randomManagerPrivateKey = PrivateKey.random(); let randomManagerAddress = randomManagerPrivateKey.toPublicKey(); - let tx = Mina.transaction(deployer, async () => { - await factory.deployRound(witness, randomManagerAddress, plotteryAddress); - }); + console.log( + `Deploying plottery: ${plotteryAddress.toBase58()} and random manager: ${randomManagerAddress.toBase58()} for round ${round}` + ); + let tx = Mina.transaction( + { sender: deployer, fee: 5 * transactionFee }, + async () => { + await factory.deployRound(witness, randomManagerAddress, plotteryAddress); + } + ); await tx.prove(); - await tx + let txInfo = await tx .sign([deployerKey, randomManagerPrivateKey, plotteryPrivateKey]) .send(); + const txResult = await txInfo.safeWait(); + + if (txResult.status === 'rejected') { + console.log(`Transaction failed due to following reason`); + console.log(txResult.toPretty()); + console.log(txResult.errors); + continue; + } + + factoryManager.addDeploy(round, randomManagerAddress, plotteryAddress); deployments[round] = { randomManager: randomManagerAddress.toBase58(), plottery: plotteryAddress.toBase58(), @@ -85,8 +119,10 @@ for (let round = +from; round <= +to; round++) { // Write result to file -if (!fs.existsSync(`./deployV2/${verificationKey.hash.toString()}`)) { - fs.mkdirSync(`./deployV2/${verificationKey.hash.toString()}`, { +if ( + !fs.existsSync(`./deployV2/${networkId}/${verificationKey.hash.toString()}`) +) { + fs.mkdirSync(`./deployV2/${networkId}/${verificationKey.hash.toString()}`, { recursive: true, }); } diff --git a/scripts/deploy_factory.ts b/scripts/deploy_factory.ts index 107ead7..510549f 100644 --- a/scripts/deploy_factory.ts +++ b/scripts/deploy_factory.ts @@ -1,16 +1,20 @@ import { AccountUpdate, Mina, PrivateKey } from 'o1js'; -import { configDefaultInstance } from './utils'; -import { PlotteryFactory } from '../src/Factory'; +import { configDefaultInstance } from './utils.js'; +import { PlotteryFactory } from '../src/Factory.js'; import * as fs from 'fs'; -configDefaultInstance(); +const { transactionFee } = configDefaultInstance(); +const networkId = Mina.activeInstance.getNetworkId().toString(); let deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); let deployer = deployerKey.toPublicKey(); +console.log(`Deploying with ${deployer.toBase58()}`); + +console.log(`Compiling PlotteryFactory`); let { verificationKey } = await PlotteryFactory.compile(); -const factoryDataPath = `./deployV2/${verificationKey.hash.toString()}/factory.json`; +const factoryDataPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/factory.json`; if (fs.existsSync(factoryDataPath)) { throw Error('Contract with same verification key already deployed'); @@ -21,26 +25,38 @@ let factoryAddress = factoryKey.toPublicKey(); let factory = new PlotteryFactory(factoryAddress); -let tx = Mina.transaction(deployer, async () => { - AccountUpdate.fundNewAccount(deployer); - factory.deploy(); -}); +console.log(`Preparing transaction`); +let tx = Mina.transaction( + { sender: deployer, fee: transactionFee }, + async () => { + AccountUpdate.fundNewAccount(deployer); + factory.deploy(); + } +); await tx.prove(); -await tx.sign([deployerKey, factoryKey]).send(); +let txInfo = await tx.sign([deployerKey, factoryKey]).send(); let deploymentData = { address: factoryAddress.toBase58(), key: factoryKey.toBase58(), }; -if (!fs.existsSync(`./deployV2/${verificationKey.hash.toString()}`)) { - fs.mkdirSync(`./deployV2/${verificationKey.hash.toString()}`, { +if ( + !fs.existsSync(`./deployV2/${networkId}/${verificationKey.hash.toString()}`) +) { + fs.mkdirSync(`./deployV2/${networkId}/${verificationKey.hash.toString()}`, { recursive: true, }); } fs.writeFileSync( - `./deployV2/${verificationKey.hash.toString()}/factory.json`, + `./deployV2/${networkId}/${verificationKey.hash.toString()}/factory.json`, JSON.stringify(deploymentData, null, 2) ); + +console.log(`Transaction hash: ${txInfo.hash}`); + +console.log('Waiting for transaction to be included in block'); + +await txInfo.wait(); diff --git a/scripts/generate_cache.ts b/scripts/generate_cache.ts index 80fb9e0..7b836a4 100644 --- a/scripts/generate_cache.ts +++ b/scripts/generate_cache.ts @@ -1,39 +1,39 @@ -import * as fs from 'fs'; -import { Cache, PublicKey } from 'o1js'; -import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; -import { getPLottery } from '../src/PLottery.js'; -import { getRandomManager } from '../src/Random/RandomManager.js'; - -// If no epoch is provided - last one will be used -let deploy_epoch = process.argv[2] ? process.argv[2] : 'current'; - -let addressesBuffer = fs.readFileSync( - `./deploy/addresses/${deploy_epoch}.json` -); -let addresses: { - randomManagerAddress: string; - lotteryAddress: string; - randomManagerOwner: string; -} = JSON.parse(addressesBuffer.toString()); - -let randomManagerAddress = PublicKey.fromBase58(addresses.randomManagerAddress); -let lotteryAddress = PublicKey.fromBase58(addresses.lotteryAddress); -let randomManagerOwner = PublicKey.fromBase58(addresses.randomManagerOwner); - -await DistributionProgram.compile({ cache: Cache.FileSystem('./cache/DP') }); -await TicketReduceProgram.compile({ cache: Cache.FileSystem('./cache/TRP') }); - -let RandomManager = getRandomManager(randomManagerOwner); - -let PLottery = getPLottery(randomManagerAddress, randomManagerOwner); - -await RandomManager.compile({ - cache: Cache.FileSystem( - `./cache/RandomManager/${addresses.randomManagerAddress}` - ), -}); - -await PLottery.compile({ - cache: Cache.FileSystem(`./cache/PLottery/${addresses.lotteryAddress}`), -}); +// import * as fs from 'fs'; +// import { Cache, PublicKey } from 'o1js'; +// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +// import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; +// import { getPLottery } from '../src/PLottery.js'; +// import { getRandomManager } from '../src/Random/RandomManager.js'; + +// // If no epoch is provided - last one will be used +// let deploy_epoch = process.argv[2] ? process.argv[2] : 'current'; + +// let addressesBuffer = fs.readFileSync( +// `./deploy/addresses/${deploy_epoch}.json` +// ); +// let addresses: { +// randomManagerAddress: string; +// lotteryAddress: string; +// randomManagerOwner: string; +// } = JSON.parse(addressesBuffer.toString()); + +// let randomManagerAddress = PublicKey.fromBase58(addresses.randomManagerAddress); +// let lotteryAddress = PublicKey.fromBase58(addresses.lotteryAddress); +// let randomManagerOwner = PublicKey.fromBase58(addresses.randomManagerOwner); + +// await DistributionProgram.compile({ cache: Cache.FileSystem('./cache/DP') }); +// await TicketReduceProgram.compile({ cache: Cache.FileSystem('./cache/TRP') }); + +// let RandomManager = getRandomManager(randomManagerOwner); + +// let PLottery = getPLottery(randomManagerAddress, randomManagerOwner); + +// await RandomManager.compile({ +// cache: Cache.FileSystem( +// `./cache/RandomManager/${addresses.randomManagerAddress}` +// ), +// }); + +// await PLottery.compile({ +// cache: Cache.FileSystem(`./cache/PLottery/${addresses.lotteryAddress}`), +// }); diff --git a/scripts/global_test/deploy_lightnet.ts b/scripts/global_test/deploy_lightnet.ts index 001fe63..df0ab7b 100644 --- a/scripts/global_test/deploy_lightnet.ts +++ b/scripts/global_test/deploy_lightnet.ts @@ -1,73 +1,73 @@ -import fs from 'fs/promises'; -import { AccountUpdate, Lightnet, Mina, NetworkId, PrivateKey } from 'o1js'; -import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; -import { getRandomManager } from '../../src/Random/RandomManager'; -import { getPLottery } from '../../src/PLottery'; -import { DistributionProgram } from '../../src/Proofs/DistributionProof'; -import { TicketReduceProgram } from '../../src/Proofs/TicketReduceProof'; +// import fs from 'fs/promises'; +// import { AccountUpdate, Lightnet, Mina, NetworkId, PrivateKey } from 'o1js'; +// import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; +// import { getRandomManager } from '../../src/Random/RandomManager'; +// import { getPLottery } from '../../src/PLottery'; +// import { DistributionProgram } from '../../src/Proofs/DistributionProof'; +// import { TicketReduceProgram } from '../../src/Proofs/TicketReduceProof'; -export const deployToLightnet = async () => { - Error.stackTraceLimit = 1000; - const DEFAULT_NETWORK_ID = 'testnet'; +// export const deployToLightnet = async () => { +// Error.stackTraceLimit = 1000; +// const DEFAULT_NETWORK_ID = 'testnet'; - const Network = Mina.Network({ - // We need to default to the testnet networkId if none is specified for this deploy alias in config.json - // This is to ensure the backward compatibility. - mina: 'http://localhost:8080/graphql', - archive: 'http://localhost:8282', - lightnetAccountManager: 'http://localhost:8181', - }); +// const Network = Mina.Network({ +// // We need to default to the testnet networkId if none is specified for this deploy alias in config.json +// // This is to ensure the backward compatibility. +// mina: 'http://localhost:8080/graphql', +// archive: 'http://localhost:8282', +// lightnetAccountManager: 'http://localhost:8181', +// }); - const fee = 1e9; // in nanomina (1 billion = 1.0 mina) - Mina.setActiveInstance(Network); +// const fee = 1e9; // in nanomina (1 billion = 1.0 mina) +// Mina.setActiveInstance(Network); - const deployer = await Lightnet.acquireKeyPair(); +// const deployer = await Lightnet.acquireKeyPair(); - const randomManagerKeys = PrivateKey.randomKeypair(); - const plotteryKeys = PrivateKey.randomKeypair(); +// const randomManagerKeys = PrivateKey.randomKeypair(); +// const plotteryKeys = PrivateKey.randomKeypair(); - // Compile everything - await ZkonZkProgram.compile(); - await ZkonRequestCoordinator.compile(); +// // Compile everything +// await ZkonZkProgram.compile(); +// await ZkonRequestCoordinator.compile(); - let RandomManager = getRandomManager(deployer.publicKey); - let randomManager = new RandomManager(randomManagerKeys.publicKey); - await RandomManager.compile(); +// let RandomManager = getRandomManager(deployer.publicKey); +// let randomManager = new RandomManager(randomManagerKeys.publicKey); +// await RandomManager.compile(); - let rmDeployTx = await Mina.transaction( - { sender: deployer.publicKey, fee }, - async () => { - AccountUpdate.fundNewAccount(randomManagerKeys.publicKey); - await randomManager.deploy(); - } - ); +// let rmDeployTx = await Mina.transaction( +// { sender: deployer.publicKey, fee }, +// async () => { +// AccountUpdate.fundNewAccount(randomManagerKeys.publicKey); +// await randomManager.deploy(); +// } +// ); - await rmDeployTx.prove(); - let rmDeployTxStatus = await rmDeployTx - .sign([deployer.privateKey, randomManagerKeys.privateKey]) - .send(); +// await rmDeployTx.prove(); +// let rmDeployTxStatus = await rmDeployTx +// .sign([deployer.privateKey, randomManagerKeys.privateKey]) +// .send(); - let Lottery = getPLottery(randomManagerKeys.publicKey, deployer.publicKey); - let lottery = new Lottery(plotteryKeys.publicKey); - await DistributionProgram.compile(); - await TicketReduceProgram.compile(); - await Lottery.compile(); +// let Lottery = getPLottery(randomManagerKeys.publicKey, deployer.publicKey); +// let lottery = new Lottery(plotteryKeys.publicKey); +// await DistributionProgram.compile(); +// await TicketReduceProgram.compile(); +// await Lottery.compile(); - let lotteryDeployTx = await Mina.transaction( - { sender: deployer.publicKey, fee }, - async () => { - AccountUpdate.fundNewAccount(deployer.publicKey); - await lottery.deploy(); - } - ); +// let lotteryDeployTx = await Mina.transaction( +// { sender: deployer.publicKey, fee }, +// async () => { +// AccountUpdate.fundNewAccount(deployer.publicKey); +// await lottery.deploy(); +// } +// ); - await lotteryDeployTx.prove(); - let lotteryDeployTxStatus = await lotteryDeployTx - .sign([deployer.privateKey, plotteryKeys.privateKey]) - .send(); +// await lotteryDeployTx.prove(); +// let lotteryDeployTxStatus = await lotteryDeployTx +// .sign([deployer.privateKey, plotteryKeys.privateKey]) +// .send(); - return { - lottery, - randomManager, - }; -}; +// return { +// lottery, +// randomManager, +// }; +// }; diff --git a/scripts/global_test/events.ts b/scripts/global_test/events.ts index 2a52ddd..e517cb2 100644 --- a/scripts/global_test/events.ts +++ b/scripts/global_test/events.ts @@ -1,419 +1,419 @@ -import { Field, MerkleList, Mina, PrivateKey, PublicKey, UInt32 } from 'o1js'; -import { - NumberPacked, - PLotteryType, - PStateManager, - Ticket, - generateNumbersSeed, -} from '../../src'; -import { randomInt } from 'crypto'; -import { LotteryAction } from '../../src/Proofs/TicketReduceProof.js'; -import { RandomManagerType } from '../../src/Random/RandomManager.js'; -import { RandomManagerManager } from '../../src/StateManager/RandomManagerManager.js'; - -const PLAYERS_AMOUNT = 10; - -const generatePlayers = () => { - return [...Array(PLAYERS_AMOUNT)].map(() => PrivateKey.randomKeypair()); -}; - -const players = generatePlayers(); - -interface TicketInfo { - ticket: Ticket; - round: Field; -} - -class Context { - lottery: PLotteryType; - randomManager: RandomManagerType; - lotterySM: PStateManager; - randomManagerSM: RandomManagerManager; - - boughtTickets: TicketInfo[]; - usedTickets: TicketInfo[]; -} - -class Deviation { - name: string; - field: string; - probability: number; - expectedError: string | undefined; - apply: () => Promise; -} - -type Account = { - publicKey: PublicKey; - privateKey: PrivateKey; -}; - -abstract class TestEvent { - activeDeviations: Deviation[]; - sender: Account; - context: Context; - - constructor(context: Context, sender: Account) { - this.context = context; - this.sender = sender; - } - - static async randomValid(context: Context): Promise { - throw Error('unimplemented'); - } - abstract getDeviations(): Deviation[]; - addDeviation(deviation: Deviation) { - this.activeDeviations.push(deviation); - deviation.apply(); - } - - async safeInvoke() { - try { - await this.invoke(); - } catch (e) { - if (e != this.activeDeviations[0].expectedError) { - throw Error('Unhandled error'); - } - } - } - abstract invoke(): Promise; - - async checkedInoke() { - let shouldFail = this.activeDeviations.length > 0; - try { - await this.invoke(); - } catch (e) { - if (shouldFail) { - console.log(`Expected error ${e} occured`); - return; - } - throw Error(`Unexpected error ${e} occured`); - } - - if (shouldFail) { - throw Error(`Expected error, but nothing heppen`); - return; - } - } -} - -class BuyTicketEvent extends TestEvent { - ownerIndex: number; - ticket: Ticket; - round: Field; - - constructor(context: Context, ownerIndex: number, ticket: Ticket) { - super(context, players[ownerIndex]); - this.ownerIndex = ownerIndex; - this.ticket = ticket; - } - - static override async randomValid(context: Context): Promise { - const ownerIndex = randomInt(PLAYERS_AMOUNT); - const owner = players[ownerIndex]; - const ticket = Ticket.random(owner.publicKey); - - return new BuyTicketEvent(context, ownerIndex, ticket); - } - - getDeviations(): Deviation[] { - return [ - { - name: 'wrong numbers', - field: 'ticket', - probability: 0.1, - apply: async () => { - this.ticket.numbers[randomInt(6)] = UInt32.from( - randomInt(10, +UInt32.MAXINT) - ); - }, - expectedError: '????', - }, - { - name: 'wrong owner', - field: 'sender', - probability: 0.1, - apply: async () => { - this.sender = players[(this.ownerIndex + randomInt(1, 10)) % 10]; - }, - expectedError: '????', - }, - { - name: 'wrong round', - field: 'round', - probability: 0.1, - apply: async () => { - // 50/50 less or greater - if (Math.random() > 0.5) { - this.round = Field(randomInt(+this.round + 1, +this.round + 1000)); - } else { - this.round = Field(randomInt(+this.round)); - } - }, - expectedError: '????', - }, - ]; - } - - async invoke() { - let tx = Mina.transaction(this.sender.publicKey, async () => { - await this.context.lottery.buyTicket(this.ticket, this.round); - }); - - await tx.prove(); - await tx.sign([this.sender.privateKey]).send(); - } -} - -// class RefundTicketEvent extends TestEvent {} - -class RedeemTicketEvent extends TestEvent { - round: Field; - ticket: Ticket; - - constructor(context: Context, round: Field, ticket: Ticket) { - super(context, players[randomInt(players.length)]); - this.round = round; - this.ticket = ticket; - } - - static override async randomValid( - context: Context - ): Promise { - const { ticket, round } = - context.boughtTickets[randomInt(context.boughtTickets.length)]; - - return new RedeemTicketEvent(context, round, ticket); - } - - getDeviations(): Deviation[] { - return [ - { - name: 'used ticket', - field: 'ticket', - probability: 0.1, - apply: async () => { - const { ticket, round } = - this.context.boughtTickets[ - randomInt(this.context.boughtTickets.length) - ]; - - this.round = round; - this.ticket = ticket; - }, - expectedError: '????', - }, - ]; - } - - async invoke() { - const rp = await this.context.lotterySM.getReward(+this.round, this.ticket); - let tx = await Mina.transaction(this.sender.publicKey, async () => { - await this.context.lottery.getReward( - this.ticket, - rp.roundWitness, - rp.roundTicketWitness, - rp.dp, - rp.winningNumbers, - rp.resultWitness, - rp.bankValue, - rp.bankWitness, - rp.nullifierWitness - ); - }); - - await tx.prove(); - await tx.sign([this.sender.privateKey]).send(); - - // Change bought ticket to used one - } -} - -// class RandomValueGenerationEvent extends TestEvent {} - -class ProduceResultEvent extends TestEvent { - round: Field; - randomRound: Field; - - constructor(context: Context, round: Field) { - super(context, players[randomInt(players.length)]); - this.round = round; - this.randomRound = round; - } - - getDeviations(): Deviation[] { - return [ - { - name: 'round-have-not-started', - field: 'round', - probability: 0.1, - expectedError: '???', - apply: async () => { - this.activeDeviations = this.activeDeviations.filter( - (v) => v.field != 'round' - ); - - this.round = Field(randomInt(+this.round + 1, +this.round + 1000)); - }, - }, - - { - name: 'round-has-result', - field: 'round', - probability: 0.1, - expectedError: '???', - apply: async () => { - this.activeDeviations = this.activeDeviations.filter( - (v) => v.field != 'round' - ); - - this.round = Field(randomInt(+this.round)); - }, - }, - - { - name: 'wrong-random-round', - field: 'randomRound', - probability: 0.1, - expectedError: '???', - apply: async () => { - let deviantRound = randomInt(+this.round * 2); - while (deviantRound == +this.round) { - deviantRound = randomInt(+this.round * 2); - } - this.randomRound = Field(deviantRound); - }, - }, - ]; - } - - async invoke() { - const resultWV = this.context.randomManagerSM.getResultWitness( - this.randomRound - ); - - const { resultWitness, bankValue, bankWitness } = - this.context.lotterySM.updateResult( - this.round, - NumberPacked.pack(generateNumbersSeed(resultWV.value)) - ); - - let tx = Mina.transaction(this.sender.publicKey, async () => { - await this.context.lottery.produceResult( - resultWitness, - bankValue, - bankWitness, - resultWV.witness, - resultWV.value - ); - }); - - await tx.prove(); - await tx.sign([this.sender.privateKey]).send(); - } -} - -class ReduceTicketsEvent extends TestEvent { - fromState: Field; - toState: Field; - actions: LotteryAction[][]; - - constructor( - context: Context, - sender: Account, - fromState: Field, - toState: Field, - actions: LotteryAction[][] - ) { - super(context, sender); - this.fromState = fromState; - this.toState = toState; - this.actions = actions; - } - - static override async randomValid( - context: Context - ): Promise { - const randomSender = players[randomInt(players.length)]; - const fromState = context.lottery.lastProcessedState.get(); - const toState = context.lottery.account.actionState.get(); - const actions = await context.lottery.reducer.fetchActions({ - fromActionState: fromState, - endActionState: toState, - }); - - return new ReduceTicketsEvent( - context, - randomSender, - fromState, - toState, - actions - ); - } - - getDeviations(): Deviation[] { - return [ - { - name: 'wrong fromState', - field: 'fromState', - probability: 0.1, - expectedError: '???', - apply: async () => { - this.fromState = this.toState; // Chenge to one step jump - this.actions = await this.context.lottery.reducer.fetchActions({ - fromActionState: this.fromState, - endActionState: this.toState, - }); - }, - }, - { - name: 'wrong actions', - field: 'actions', - probability: 0.1, - expectedError: '???', - apply: async () => { - let firstIndex = randomInt(this.actions.length); - let secondIndex = randomInt( - this.actions[randomInt(firstIndex)].length - ); - this.actions[firstIndex][secondIndex].ticket.numbers[0] = - this.actions[firstIndex][secondIndex].ticket.numbers[0] - .add(1) - .mod(10) - .add(1); - }, - }, - ]; - } - - async invoke() { - const shouldFail = this.activeDeviations.length > 0; - let reduceProof = await this.context.lotterySM.reduceTickets( - this.fromState, - this.actions, - !shouldFail - ); - - let tx = Mina.transaction(this.sender.publicKey, async () => { - await this.context.lottery.reduceTickets(reduceProof); - }); - - await tx.prove(); - await tx.sign([this.sender.privateKey]).send(); - } -} - -const events = [BuyTicketEvent, ReduceTicketsEvent]; - -export class TestOperator { - async invokeNextEvent(context: Context) { - const eventType = events[randomInt(events.length)]; - const event = await eventType.randomValid(context); - const deviations = event.getDeviations(); - deviations.forEach((deviation) => { - if (deviation.probability > Math.random()) { - event.addDeviation(deviation); - } - }); - await event.invoke(); - } -} +// import { Field, MerkleList, Mina, PrivateKey, PublicKey, UInt32 } from 'o1js'; +// import { +// NumberPacked, +// PLotteryType, +// PStateManager, +// Ticket, +// generateNumbersSeed, +// } from '../../src'; +// import { randomInt } from 'crypto'; +// import { LotteryAction } from '../../src/Proofs/TicketReduceProof.js'; +// import { RandomManagerType } from '../../src/Random/RandomManager.js'; +// import { RandomManagerManager } from '../../src/StateManager/RandomManagerManager.js'; + +// const PLAYERS_AMOUNT = 10; + +// const generatePlayers = () => { +// return [...Array(PLAYERS_AMOUNT)].map(() => PrivateKey.randomKeypair()); +// }; + +// const players = generatePlayers(); + +// interface TicketInfo { +// ticket: Ticket; +// round: Field; +// } + +// class Context { +// lottery: PLotteryType; +// randomManager: RandomManagerType; +// lotterySM: PStateManager; +// randomManagerSM: RandomManagerManager; + +// boughtTickets: TicketInfo[]; +// usedTickets: TicketInfo[]; +// } + +// class Deviation { +// name: string; +// field: string; +// probability: number; +// expectedError: string | undefined; +// apply: () => Promise; +// } + +// type Account = { +// publicKey: PublicKey; +// privateKey: PrivateKey; +// }; + +// abstract class TestEvent { +// activeDeviations: Deviation[]; +// sender: Account; +// context: Context; + +// constructor(context: Context, sender: Account) { +// this.context = context; +// this.sender = sender; +// } + +// static async randomValid(context: Context): Promise { +// throw Error('unimplemented'); +// } +// abstract getDeviations(): Deviation[]; +// addDeviation(deviation: Deviation) { +// this.activeDeviations.push(deviation); +// deviation.apply(); +// } + +// async safeInvoke() { +// try { +// await this.invoke(); +// } catch (e) { +// if (e != this.activeDeviations[0].expectedError) { +// throw Error('Unhandled error'); +// } +// } +// } +// abstract invoke(): Promise; + +// async checkedInoke() { +// let shouldFail = this.activeDeviations.length > 0; +// try { +// await this.invoke(); +// } catch (e) { +// if (shouldFail) { +// console.log(`Expected error ${e} occured`); +// return; +// } +// throw Error(`Unexpected error ${e} occured`); +// } + +// if (shouldFail) { +// throw Error(`Expected error, but nothing heppen`); +// return; +// } +// } +// } + +// class BuyTicketEvent extends TestEvent { +// ownerIndex: number; +// ticket: Ticket; +// round: Field; + +// constructor(context: Context, ownerIndex: number, ticket: Ticket) { +// super(context, players[ownerIndex]); +// this.ownerIndex = ownerIndex; +// this.ticket = ticket; +// } + +// static override async randomValid(context: Context): Promise { +// const ownerIndex = randomInt(PLAYERS_AMOUNT); +// const owner = players[ownerIndex]; +// const ticket = Ticket.random(owner.publicKey); + +// return new BuyTicketEvent(context, ownerIndex, ticket); +// } + +// getDeviations(): Deviation[] { +// return [ +// { +// name: 'wrong numbers', +// field: 'ticket', +// probability: 0.1, +// apply: async () => { +// this.ticket.numbers[randomInt(6)] = UInt32.from( +// randomInt(10, +UInt32.MAXINT) +// ); +// }, +// expectedError: '????', +// }, +// { +// name: 'wrong owner', +// field: 'sender', +// probability: 0.1, +// apply: async () => { +// this.sender = players[(this.ownerIndex + randomInt(1, 10)) % 10]; +// }, +// expectedError: '????', +// }, +// { +// name: 'wrong round', +// field: 'round', +// probability: 0.1, +// apply: async () => { +// // 50/50 less or greater +// if (Math.random() > 0.5) { +// this.round = Field(randomInt(+this.round + 1, +this.round + 1000)); +// } else { +// this.round = Field(randomInt(+this.round)); +// } +// }, +// expectedError: '????', +// }, +// ]; +// } + +// async invoke() { +// let tx = Mina.transaction(this.sender.publicKey, async () => { +// await this.context.lottery.buyTicket(this.ticket, this.round); +// }); + +// await tx.prove(); +// await tx.sign([this.sender.privateKey]).send(); +// } +// } + +// // class RefundTicketEvent extends TestEvent {} + +// class RedeemTicketEvent extends TestEvent { +// round: Field; +// ticket: Ticket; + +// constructor(context: Context, round: Field, ticket: Ticket) { +// super(context, players[randomInt(players.length)]); +// this.round = round; +// this.ticket = ticket; +// } + +// static override async randomValid( +// context: Context +// ): Promise { +// const { ticket, round } = +// context.boughtTickets[randomInt(context.boughtTickets.length)]; + +// return new RedeemTicketEvent(context, round, ticket); +// } + +// getDeviations(): Deviation[] { +// return [ +// { +// name: 'used ticket', +// field: 'ticket', +// probability: 0.1, +// apply: async () => { +// const { ticket, round } = +// this.context.boughtTickets[ +// randomInt(this.context.boughtTickets.length) +// ]; + +// this.round = round; +// this.ticket = ticket; +// }, +// expectedError: '????', +// }, +// ]; +// } + +// async invoke() { +// const rp = await this.context.lotterySM.getReward(+this.round, this.ticket); +// let tx = await Mina.transaction(this.sender.publicKey, async () => { +// await this.context.lottery.getReward( +// this.ticket, +// rp.roundWitness, +// rp.roundTicketWitness, +// rp.dp, +// rp.winningNumbers, +// rp.resultWitness, +// rp.bankValue, +// rp.bankWitness, +// rp.nullifierWitness +// ); +// }); + +// await tx.prove(); +// await tx.sign([this.sender.privateKey]).send(); + +// // Change bought ticket to used one +// } +// } + +// // class RandomValueGenerationEvent extends TestEvent {} + +// class ProduceResultEvent extends TestEvent { +// round: Field; +// randomRound: Field; + +// constructor(context: Context, round: Field) { +// super(context, players[randomInt(players.length)]); +// this.round = round; +// this.randomRound = round; +// } + +// getDeviations(): Deviation[] { +// return [ +// { +// name: 'round-have-not-started', +// field: 'round', +// probability: 0.1, +// expectedError: '???', +// apply: async () => { +// this.activeDeviations = this.activeDeviations.filter( +// (v) => v.field != 'round' +// ); + +// this.round = Field(randomInt(+this.round + 1, +this.round + 1000)); +// }, +// }, + +// { +// name: 'round-has-result', +// field: 'round', +// probability: 0.1, +// expectedError: '???', +// apply: async () => { +// this.activeDeviations = this.activeDeviations.filter( +// (v) => v.field != 'round' +// ); + +// this.round = Field(randomInt(+this.round)); +// }, +// }, + +// { +// name: 'wrong-random-round', +// field: 'randomRound', +// probability: 0.1, +// expectedError: '???', +// apply: async () => { +// let deviantRound = randomInt(+this.round * 2); +// while (deviantRound == +this.round) { +// deviantRound = randomInt(+this.round * 2); +// } +// this.randomRound = Field(deviantRound); +// }, +// }, +// ]; +// } + +// async invoke() { +// const resultWV = this.context.randomManagerSM.getResultWitness( +// this.randomRound +// ); + +// const { resultWitness, bankValue, bankWitness } = +// this.context.lotterySM.updateResult( +// this.round, +// NumberPacked.pack(generateNumbersSeed(resultWV.value)) +// ); + +// let tx = Mina.transaction(this.sender.publicKey, async () => { +// await this.context.lottery.produceResult( +// resultWitness, +// bankValue, +// bankWitness, +// resultWV.witness, +// resultWV.value +// ); +// }); + +// await tx.prove(); +// await tx.sign([this.sender.privateKey]).send(); +// } +// } + +// class ReduceTicketsEvent extends TestEvent { +// fromState: Field; +// toState: Field; +// actions: LotteryAction[][]; + +// constructor( +// context: Context, +// sender: Account, +// fromState: Field, +// toState: Field, +// actions: LotteryAction[][] +// ) { +// super(context, sender); +// this.fromState = fromState; +// this.toState = toState; +// this.actions = actions; +// } + +// static override async randomValid( +// context: Context +// ): Promise { +// const randomSender = players[randomInt(players.length)]; +// const fromState = context.lottery.lastProcessedState.get(); +// const toState = context.lottery.account.actionState.get(); +// const actions = await context.lottery.reducer.fetchActions({ +// fromActionState: fromState, +// endActionState: toState, +// }); + +// return new ReduceTicketsEvent( +// context, +// randomSender, +// fromState, +// toState, +// actions +// ); +// } + +// getDeviations(): Deviation[] { +// return [ +// { +// name: 'wrong fromState', +// field: 'fromState', +// probability: 0.1, +// expectedError: '???', +// apply: async () => { +// this.fromState = this.toState; // Chenge to one step jump +// this.actions = await this.context.lottery.reducer.fetchActions({ +// fromActionState: this.fromState, +// endActionState: this.toState, +// }); +// }, +// }, +// { +// name: 'wrong actions', +// field: 'actions', +// probability: 0.1, +// expectedError: '???', +// apply: async () => { +// let firstIndex = randomInt(this.actions.length); +// let secondIndex = randomInt( +// this.actions[randomInt(firstIndex)].length +// ); +// this.actions[firstIndex][secondIndex].ticket.numbers[0] = +// this.actions[firstIndex][secondIndex].ticket.numbers[0] +// .add(1) +// .mod(10) +// .add(1); +// }, +// }, +// ]; +// } + +// async invoke() { +// const shouldFail = this.activeDeviations.length > 0; +// let reduceProof = await this.context.lotterySM.reduceTickets( +// this.fromState, +// this.actions, +// !shouldFail +// ); + +// let tx = Mina.transaction(this.sender.publicKey, async () => { +// await this.context.lottery.reduceTickets(reduceProof); +// }); + +// await tx.prove(); +// await tx.sign([this.sender.privateKey]).send(); +// } +// } + +// const events = [BuyTicketEvent, ReduceTicketsEvent]; + +// export class TestOperator { +// async invokeNextEvent(context: Context) { +// const eventType = events[randomInt(events.length)]; +// const event = await eventType.randomValid(context); +// const deviations = event.getDeviations(); +// deviations.forEach((deviation) => { +// if (deviation.probability > Math.random()) { +// event.addDeviation(deviation); +// } +// }); +// await event.invoke(); +// } +// } diff --git a/scripts/global_test/global_test.ts b/scripts/global_test/global_test.ts index 5d4264f..947de19 100644 --- a/scripts/global_test/global_test.ts +++ b/scripts/global_test/global_test.ts @@ -1,26 +1,26 @@ -import fs from 'fs/promises'; -import { AccountUpdate, Lightnet, Mina, NetworkId, PrivateKey } from 'o1js'; -import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; -import { getRandomManager } from '../../src/Random/RandomManager'; -import { getPLottery } from '../../src/PLottery'; -import { DistributionProgram } from '../../src/Proofs/DistributionProof'; -import { TicketReduceProgram } from '../../src/Proofs/TicketReduceProof'; -import { deployToLightnet } from './deploy_lightnet'; -import { TestOperator } from './events'; +// import fs from 'fs/promises'; +// import { AccountUpdate, Lightnet, Mina, NetworkId, PrivateKey } from 'o1js'; +// import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; +// import { getRandomManager } from '../../src/Random/RandomManager'; +// import { getPLottery } from '../../src/PLottery'; +// import { DistributionProgram } from '../../src/Proofs/DistributionProof'; +// import { TicketReduceProgram } from '../../src/Proofs/TicketReduceProof'; +// import { deployToLightnet } from './deploy_lightnet'; +// import { TestOperator } from './events'; -function sleep(ms: number) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} +// function sleep(ms: number) { +// return new Promise((resolve) => { +// setTimeout(resolve, ms); +// }); +// } -const iterations = 1000; -const waitAmount = 30000; -const { lottery, randomManager } = await deployToLightnet(); +// const iterations = 1000; +// const waitAmount = 30000; +// const { lottery, randomManager } = await deployToLightnet(); -const operator = new TestOperator(); +// const operator = new TestOperator(); -// for (let i = 0; i < iterations; i++) { -// await operator.invokeNextEvent(lottery); -// await sleep(waitAmount); -// } +// // for (let i = 0; i < iterations; i++) { +// // await operator.invokeNextEvent(lottery); +// // await sleep(waitAmount); +// // } diff --git a/scripts/hash_proof.ts b/scripts/hash_proof.ts index f209bd2..c32abce 100644 --- a/scripts/hash_proof.ts +++ b/scripts/hash_proof.ts @@ -1,31 +1,31 @@ -import axios from 'axios'; +// import axios from 'axios'; -let response = await axios.post( - 'https://api.minascan.io/node/devnet/v1/graphql', - JSON.stringify({ - query: ` - - query { - block (stateHash: "jxydnwVuHCGohXntpNtsYwt83x4V1yK4iGsAxENKBZ4zYD6u8xN") { - stateHash - protocolState { - consensusState (blockHeight: 4) { +// let response = await axios.post( +// 'https://api.minascan.io/node/devnet/v1/graphql', +// JSON.stringify({ +// query: ` - } - } - } - } - -`, - }), - { - headers: { - 'Content-Type': 'application/json', - }, - responseType: 'json', - } -); +// query { +// block (stateHash: "jxydnwVuHCGohXntpNtsYwt83x4V1yK4iGsAxENKBZ4zYD6u8xN") { +// stateHash +// protocolState { +// consensusState (blockHeight: 4) { -console.log(response.data); +// } +// } +// } +// } -console.log(JSON.stringify(response.data)); +// `, +// }), +// { +// headers: { +// 'Content-Type': 'application/json', +// }, +// responseType: 'json', +// } +// ); + +// console.log(response.data); + +// console.log(JSON.stringify(response.data)); diff --git a/scripts/pbuy_ticket.ts b/scripts/pbuy_ticket.ts index 76f9463..4d4bba0 100644 --- a/scripts/pbuy_ticket.ts +++ b/scripts/pbuy_ticket.ts @@ -1,118 +1,118 @@ -/** - * This script can be used to interact with the Add contract, after deploying it. - * - * We call the update() method on the contract, create a proof and send it to the chain. - * The endpoint that we interact with is read from your config.json. - * - * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens - * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet - * would send the transaction and pay the fee. - * - * To run locally: - * Build the project: `$ npm run build` - * Run with node: `$ node build/scripts/buy_ticket.js `. - */ -import fs from 'fs/promises'; -import { Cache, Field, Mina, NetworkId, PrivateKey, fetchAccount } from 'o1js'; -import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -import { Ticket } from '../src/Structs/Ticket.js'; -import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; -import { PStateManager } from '../src/StateManager/PStateManager.js'; -import { findPlottery } from './utils.js'; - -// check command line arg -let deployAlias = process.argv[2]; -if (!deployAlias) - throw Error(`Missing argument. - -Usage: -node build/src/interact.js -`); -Error.stackTraceLimit = 1000; -const DEFAULT_NETWORK_ID = 'testnet'; - -// parse config and private key from file -type Config = { - deployAliases: Record< - string, - { - networkId?: string; - url: string; - keyPath: string; - fee: string; - feepayerKeyPath: string; - feepayerAlias: string; - } - >; -}; -let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); -let config = configJson.deployAliases[deployAlias]; -let feepayerKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.feepayerKeyPath, 'utf8') -); - -let zkAppKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.keyPath, 'utf8') -); - -let feepayerKey = PrivateKey.fromBase58(feepayerKeysBase58.privateKey); -let zkAppKey = PrivateKey.fromBase58(zkAppKeysBase58.privateKey); - -// set up Mina instance and contract we interact with -const Network = Mina.Network({ - // We need to default to the testnet networkId if none is specified for this deploy alias in config.json - // This is to ensure the backward compatibility. - networkId: (config.networkId ?? DEFAULT_NETWORK_ID) as NetworkId, - mina: config.url, -}); -// const Network = Mina.Network(config.url); -const fee = Number(config.fee) * 1e9; // in nanomina (1 billion = 1.0 mina) -Mina.setActiveInstance(Network); -let feepayerAddress = feepayerKey.toPublicKey(); -let zkAppAddress = zkAppKey.toPublicKey(); - -let { plottery: lottery, PLottery } = findPlottery(); - -// compile the contract to create prover keys -console.log('compile the DP'); -await DistributionProgram.compile(); -console.log('compile reduce proof'); -await TicketReduceProgram.compile(); -console.log('compile the Lottery'); -let lotteryCompileResult = await PLottery.compile(); -// let mockLotteryCompileResult = await MockLottery.compile({ -// cache: Cache.FileSystem('../cache'), -// }); +// /** +// * This script can be used to interact with the Add contract, after deploying it. +// * +// * We call the update() method on the contract, create a proof and send it to the chain. +// * The endpoint that we interact with is read from your config.json. +// * +// * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens +// * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet +// * would send the transaction and pay the fee. +// * +// * To run locally: +// * Build the project: `$ npm run build` +// * Run with node: `$ node build/scripts/buy_ticket.js `. +// */ +// import fs from 'fs/promises'; +// import { Cache, Field, Mina, NetworkId, PrivateKey, fetchAccount } from 'o1js'; +// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +// import { Ticket } from '../src/Structs/Ticket.js'; +// import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; +// import { PStateManager } from '../src/StateManager/PStateManager.js'; +// import { findPlottery } from './utils.js'; + +// // check command line arg +// let deployAlias = process.argv[2]; +// if (!deployAlias) +// throw Error(`Missing argument. + +// Usage: +// node build/src/interact.js +// `); +// Error.stackTraceLimit = 1000; +// const DEFAULT_NETWORK_ID = 'testnet'; + +// // parse config and private key from file +// type Config = { +// deployAliases: Record< +// string, +// { +// networkId?: string; +// url: string; +// keyPath: string; +// fee: string; +// feepayerKeyPath: string; +// feepayerAlias: string; +// } +// >; +// }; +// let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); +// let config = configJson.deployAliases[deployAlias]; +// let feepayerKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( +// await fs.readFile(config.feepayerKeyPath, 'utf8') +// ); + +// let zkAppKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( +// await fs.readFile(config.keyPath, 'utf8') +// ); -console.log(`Fetch: ${zkAppAddress.toBase58()}`); -console.log(`Onchain VK: `, lottery.account.verificationKey); -console.log( - `Local VK1: `, - lotteryCompileResult.verificationKey.hash.toString() -); +// let feepayerKey = PrivateKey.fromBase58(feepayerKeysBase58.privateKey); +// let zkAppKey = PrivateKey.fromBase58(zkAppKeysBase58.privateKey); + +// // set up Mina instance and contract we interact with +// const Network = Mina.Network({ +// // We need to default to the testnet networkId if none is specified for this deploy alias in config.json +// // This is to ensure the backward compatibility. +// networkId: (config.networkId ?? DEFAULT_NETWORK_ID) as NetworkId, +// mina: config.url, +// }); +// // const Network = Mina.Network(config.url); +// const fee = Number(config.fee) * 1e9; // in nanomina (1 billion = 1.0 mina) +// Mina.setActiveInstance(Network); +// let feepayerAddress = feepayerKey.toPublicKey(); +// let zkAppAddress = zkAppKey.toPublicKey(); + +// let { plottery: lottery, PLottery } = findPlottery(); + +// // compile the contract to create prover keys +// console.log('compile the DP'); +// await DistributionProgram.compile(); +// console.log('compile reduce proof'); +// await TicketReduceProgram.compile(); +// console.log('compile the Lottery'); +// let lotteryCompileResult = await PLottery.compile(); +// // let mockLotteryCompileResult = await MockLottery.compile({ +// // cache: Cache.FileSystem('../cache'), +// // }); + +// console.log(`Fetch: ${zkAppAddress.toBase58()}`); +// console.log(`Onchain VK: `, lottery.account.verificationKey); // console.log( -// `Local VK2: `, -// mockLotteryCompileResult.verificationKey.hash.toString() +// `Local VK1: `, +// lotteryCompileResult.verificationKey.hash.toString() // ); -await fetchAccount({ publicKey: zkAppAddress }); -await fetchAccount({ - publicKey: 'B62qnBkcyABfjz2cqJPzNZKjVt9M9kx1vgoiWLbkJUnk16Cz8KX8qC4', -}); +// // console.log( +// // `Local VK2: `, +// // mockLotteryCompileResult.verificationKey.hash.toString() +// // ); +// await fetchAccount({ publicKey: zkAppAddress }); +// await fetchAccount({ +// publicKey: 'B62qnBkcyABfjz2cqJPzNZKjVt9M9kx1vgoiWLbkJUnk16Cz8KX8qC4', +// }); -console.log(lottery.bankRoot.get().toString()); +// console.log(lottery.bankRoot.get().toString()); -console.log(lottery.ticketRoot.get().toString()); +// console.log(lottery.ticketRoot.get().toString()); -const state = new PStateManager(lottery, lottery.startBlock.get().value); +// const state = new PStateManager(lottery, lottery.startBlock.get().value); -const ticket = Ticket.from([1, 1, 1, 1, 1, 1], feepayerAddress, 1); +// const ticket = Ticket.from([1, 1, 1, 1, 1, 1], feepayerAddress, 1); -// console.log(`Digest: `, await MockLottery.digest()); +// // console.log(`Digest: `, await MockLottery.digest()); -let tx = await Mina.transaction({ sender: feepayerAddress, fee }, async () => { - await lottery.buyTicket(ticket, Field(0)); -}); -await tx.prove(); -let txResult = await tx.sign([feepayerKey]).send(); +// let tx = await Mina.transaction({ sender: feepayerAddress, fee }, async () => { +// await lottery.buyTicket(ticket, Field(0)); +// }); +// await tx.prove(); +// let txResult = await tx.sign([feepayerKey]).send(); -console.log(`Tx successful. Hash: `, txResult.hash); +// console.log(`Tx successful. Hash: `, txResult.hash); diff --git a/scripts/prepare_cache.ts b/scripts/prepare_cache.ts index 03eba4b..e554e3a 100644 --- a/scripts/prepare_cache.ts +++ b/scripts/prepare_cache.ts @@ -1,22 +1,22 @@ -import path from 'path'; -import { readdir, stat, copyFile, writeFile } from 'fs/promises'; +// import path from 'path'; +// import { readdir, stat, copyFile, writeFile } from 'fs/promises'; -// #TODO should be updated with current cache structure +// // #TODO should be updated with current cache structure -const directory = 'cache'; +// const directory = 'cache'; -const files = await readdir(directory); -const stats = await Promise.all( - files.map((file) => stat(path.join(directory, file))) -); +// const files = await readdir(directory); +// const stats = await Promise.all( +// files.map((file) => stat(path.join(directory, file))) +// ); -const filesToInclude = files.filter((x, i) => stats[i].size < 100_000_000); +// const filesToInclude = files.filter((x, i) => stats[i].size < 100_000_000); -for (let fileToInclude of filesToInclude) { - await copyFile(`cache/${fileToInclude}`, `cache_frontend/${fileToInclude}`); -} +// for (let fileToInclude of filesToInclude) { +// await copyFile(`cache/${fileToInclude}`, `cache_frontend/${fileToInclude}`); +// } -await writeFile( - 'cache_frontend/cache_list.json', - JSON.stringify(filesToInclude, null, 2) -); +// await writeFile( +// 'cache_frontend/cache_list.json', +// JSON.stringify(filesToInclude, null, 2) +// ); diff --git a/scripts/proof.ts b/scripts/proof.ts index fb1bbef..c6e2e11 100644 --- a/scripts/proof.ts +++ b/scripts/proof.ts @@ -1,139 +1,139 @@ -/** - * This script can be used to interact with the Add contract, after deploying it. - * - * We call the update() method on the contract, create a proof and send it to the chain. - * The endpoint that we interact with is read from your config.json. - * - * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens - * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet - * would send the transaction and pay the fee. - * - * To run locally: - * Build the project: `$ npm run build` - * Run with node: `$ node build/scripts/buy_ticket.js `. - */ -import fs from 'fs/promises'; -import { - Cache, - Mina, - NetworkId, - PrivateKey, - UInt32, - fetchAccount, - fetchLastBlock, -} from 'o1js'; -import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; -import { PStateManager } from '../src/StateManager/PStateManager.js'; -import { BLOCK_PER_ROUND } from '../src/constants.js'; -import { findPlottery } from './utils.js'; - -// check command line arg -let deployAlias = process.argv[2]; -if (!deployAlias) - throw Error(`Missing argument. - -Usage: -node build/src/interact.js -`); -Error.stackTraceLimit = 1000; -const DEFAULT_NETWORK_ID = 'testnet'; - -// parse config and private key from file -type Config = { - deployAliases: Record< - string, - { - networkId?: string; - url: string; - keyPath: string; - fee: string; - feepayerKeyPath: string; - feepayerAlias: string; - } - >; -}; -let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); -let config = configJson.deployAliases[deployAlias]; -let feepayerKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.feepayerKeyPath, 'utf8') -); - -let zkAppKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.keyPath, 'utf8') -); - -let feepayerKey = PrivateKey.fromBase58(feepayerKeysBase58.privateKey); -let zkAppKey = PrivateKey.fromBase58(zkAppKeysBase58.privateKey); - -// set up Mina instance and contract we interact with -const Network = Mina.Network({ - // We need to default to the testnet networkId if none is specified for this deploy alias in config.json - // This is to ensure the backward compatibility. - networkId: (config.networkId ?? DEFAULT_NETWORK_ID) as NetworkId, - // graphql: 'https://api.minascan.io/node/devnet/v1/graphql', - archive: 'https://api.minascan.io/archive/devnet/v1/graphql', - mina: config.url, -}); -// const Network = Mina.Network(config.url); -const fee = Number(config.fee) * 1e9; // in nanomina (1 billion = 1.0 mina) -Mina.setActiveInstance(Network); -let feepayerAddress = feepayerKey.toPublicKey(); -let zkAppAddress = zkAppKey.toPublicKey(); -let { plottery: lottery, PLottery } = findPlottery(); - -// compile the contract to create prover keys -console.log('compile the DP'); -await DistributionProgram.compile(); -console.log('compile reduce proof'); -await TicketReduceProgram.compile(); -console.log('compile the Lottery'); -let lotteryCompileResult = await PLottery.compile(); - -// let mockLotteryCompileResult = await MockLottery.compile({ -// cache: Cache.FileSystem('../cache'), -// }); +// /** +// * This script can be used to interact with the Add contract, after deploying it. +// * +// * We call the update() method on the contract, create a proof and send it to the chain. +// * The endpoint that we interact with is read from your config.json. +// * +// * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens +// * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet +// * would send the transaction and pay the fee. +// * +// * To run locally: +// * Build the project: `$ npm run build` +// * Run with node: `$ node build/scripts/buy_ticket.js `. +// */ +// import fs from 'fs/promises'; +// import { +// Cache, +// Mina, +// NetworkId, +// PrivateKey, +// UInt32, +// fetchAccount, +// fetchLastBlock, +// } from 'o1js'; +// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +// import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; +// import { PStateManager } from '../src/StateManager/PStateManager.js'; +// import { BLOCK_PER_ROUND } from '../src/constants.js'; +// import { findPlottery } from './utils.js'; + +// // check command line arg +// let deployAlias = process.argv[2]; +// if (!deployAlias) +// throw Error(`Missing argument. + +// Usage: +// node build/src/interact.js +// `); +// Error.stackTraceLimit = 1000; +// const DEFAULT_NETWORK_ID = 'testnet'; + +// // parse config and private key from file +// type Config = { +// deployAliases: Record< +// string, +// { +// networkId?: string; +// url: string; +// keyPath: string; +// fee: string; +// feepayerKeyPath: string; +// feepayerAlias: string; +// } +// >; +// }; +// let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); +// let config = configJson.deployAliases[deployAlias]; +// let feepayerKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( +// await fs.readFile(config.feepayerKeyPath, 'utf8') +// ); + +// let zkAppKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( +// await fs.readFile(config.keyPath, 'utf8') +// ); -console.log(`Fetch: ${zkAppAddress.toBase58()}`); -console.log(`Onchain VK: `, lottery.account.verificationKey); -console.log( - `Local VK1: `, - lotteryCompileResult.verificationKey.hash.toString() -); +// let feepayerKey = PrivateKey.fromBase58(feepayerKeysBase58.privateKey); +// let zkAppKey = PrivateKey.fromBase58(zkAppKeysBase58.privateKey); + +// // set up Mina instance and contract we interact with +// const Network = Mina.Network({ +// // We need to default to the testnet networkId if none is specified for this deploy alias in config.json +// // This is to ensure the backward compatibility. +// networkId: (config.networkId ?? DEFAULT_NETWORK_ID) as NetworkId, +// // graphql: 'https://api.minascan.io/node/devnet/v1/graphql', +// archive: 'https://api.minascan.io/archive/devnet/v1/graphql', +// mina: config.url, +// }); +// // const Network = Mina.Network(config.url); +// const fee = Number(config.fee) * 1e9; // in nanomina (1 billion = 1.0 mina) +// Mina.setActiveInstance(Network); +// let feepayerAddress = feepayerKey.toPublicKey(); +// let zkAppAddress = zkAppKey.toPublicKey(); +// let { plottery: lottery, PLottery } = findPlottery(); + +// // compile the contract to create prover keys +// console.log('compile the DP'); +// await DistributionProgram.compile(); +// console.log('compile reduce proof'); +// await TicketReduceProgram.compile(); +// console.log('compile the Lottery'); +// let lotteryCompileResult = await PLottery.compile(); + +// // let mockLotteryCompileResult = await MockLottery.compile({ +// // cache: Cache.FileSystem('../cache'), +// // }); + +// console.log(`Fetch: ${zkAppAddress.toBase58()}`); +// console.log(`Onchain VK: `, lottery.account.verificationKey); // console.log( -// `Local VK2: `, -// mockLotteryCompileResult.verificationKey.hash.toString() +// `Local VK1: `, +// lotteryCompileResult.verificationKey.hash.toString() // ); -await fetchAccount({ publicKey: zkAppAddress }); -await fetchAccount({ - publicKey: 'B62qnBkcyABfjz2cqJPzNZKjVt9M9kx1vgoiWLbkJUnk16Cz8KX8qC4', -}); +// // console.log( +// // `Local VK2: `, +// // mockLotteryCompileResult.verificationKey.hash.toString() +// // ); +// await fetchAccount({ publicKey: zkAppAddress }); +// await fetchAccount({ +// publicKey: 'B62qnBkcyABfjz2cqJPzNZKjVt9M9kx1vgoiWLbkJUnk16Cz8KX8qC4', +// }); -console.log(lottery.bankRoot.get().toString()); +// console.log(lottery.bankRoot.get().toString()); -console.log(lottery.ticketRoot.get().toString()); +// console.log(lottery.ticketRoot.get().toString()); -const startSlot = lottery.startBlock.get(); +// const startSlot = lottery.startBlock.get(); -const state = new PStateManager(lottery, startSlot.value, false); +// const state = new PStateManager(lottery, startSlot.value, false); -// const curSlot = lottery.network.globalSlotSinceGenesis.get(); +// // const curSlot = lottery.network.globalSlotSinceGenesis.get(); -// const curRound = curSlot.sub(startSlot).div(BLOCK_PER_ROUND); +// // const curRound = curSlot.sub(startSlot).div(BLOCK_PER_ROUND); -const curRound = UInt32.from(8); +// const curRound = UInt32.from(8); -console.log('Generate reduce proof'); -const reduceProof = await state.reduceTickets(); +// console.log('Generate reduce proof'); +// const reduceProof = await state.reduceTickets(); -// console.log(`Digest: `, await MockLottery.digest()); +// // console.log(`Digest: `, await MockLottery.digest()); -console.log('Send reduce transaction'); +// console.log('Send reduce transaction'); -let tx = await Mina.transaction({ sender: feepayerAddress, fee }, async () => { - await lottery.reduceTickets(reduceProof); -}); -await tx.prove(); -let txResult = await tx.sign([feepayerKey]).send(); +// let tx = await Mina.transaction({ sender: feepayerAddress, fee }, async () => { +// await lottery.reduceTickets(reduceProof); +// }); +// await tx.prove(); +// let txResult = await tx.sign([feepayerKey]).send(); -console.log(`Tx successful. Hash: `, txResult.hash); +// console.log(`Tx successful. Hash: `, txResult.hash); diff --git a/scripts/publish_request.ts b/scripts/publish_request.ts index 9124acc..29a714f 100644 --- a/scripts/publish_request.ts +++ b/scripts/publish_request.ts @@ -1,28 +1,28 @@ -import dotenv from 'dotenv'; -dotenv.config(); +// import dotenv from 'dotenv'; +// dotenv.config(); -import { readFileSync, writeFileSync } from 'fs'; -import { PinataSDK } from 'pinata'; +// import { readFileSync, writeFileSync } from 'fs'; +// import { PinataSDK } from 'pinata'; -// bafkreif2ett25ddjcevhnmaxmimkjdoigtsaj6bfyfil5gu65l2r6luxqm +// // bafkreif2ett25ddjcevhnmaxmimkjdoigtsaj6bfyfil5gu65l2r6luxqm -const pinata = new PinataSDK({ - pinataJwt: process.env.PINATA_JWT!, - pinataGateway: process.env.PINATA_GATEWAY, -}); +// const pinata = new PinataSDK({ +// pinataJwt: process.env.PINATA_JWT!, +// pinataGateway: process.env.PINATA_GATEWAY, +// }); -const contractCode = readFileSync('./build/src/Random/RandomManager.js'); +// const contractCode = readFileSync('./build/src/Random/RandomManager.js'); -const json = { - method: 'GET', - baseURL: 'https://quantum-random.com/quantum', - path: 'seed', - zkapp: contractCode.toString(), -}; +// const json = { +// method: 'GET', +// baseURL: 'https://quantum-random.com/quantum', +// path: 'seed', +// zkapp: contractCode.toString(), +// }; -let response = await pinata.upload.json(json); +// let response = await pinata.upload.json(json); -console.log(response.IpfsHash); -writeFileSync('./random_request_cid', response.IpfsHash.toString()); +// console.log(response.IpfsHash); +// writeFileSync('./random_request_cid', response.IpfsHash.toString()); -writeFileSync('./random_request_file', JSON.stringify(json, null, 2)); +// writeFileSync('./random_request_file', JSON.stringify(json, null, 2)); diff --git a/scripts/reveal_value.ts b/scripts/reveal_value.ts index 9017c95..7a74960 100644 --- a/scripts/reveal_value.ts +++ b/scripts/reveal_value.ts @@ -1,53 +1,53 @@ -import { Field, Mina, Poseidon } from 'o1js'; -import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; -import { - compileRandomManager, - configDefaultInstance, - findPlottery, - findRandomManager, - getDeployer, - getRMStoreManager, - storeRMStoreManager, -} from './utils'; -import { CommitValue } from '../src/Random/RandomManager'; +// import { Field, Mina, Poseidon } from 'o1js'; +// import { RandomManagerManager } from '../src/StateManager/RandomManagerManager'; +// import { +// compileRandomManager, +// configDefaultInstance, +// findPlottery, +// findRandomManager, +// getDeployer, +// getRMStoreManager, +// storeRMStoreManager, +// } from './utils'; +// import { CommitValue } from '../src/Random/RandomManager'; -configDefaultInstance(); +// configDefaultInstance(); -let round = process.argv[2]; +// let round = process.argv[2]; -if (!round) { - throw Error(`You should specify round`); -} +// if (!round) { +// throw Error(`You should specify round`); +// } -let deploy_epoch = process.argv[3] ? process.argv[3] : 'current'; +// let deploy_epoch = process.argv[3] ? process.argv[3] : 'current'; -let { deployer, deployerKey } = getDeployer(); +// let { deployer, deployerKey } = getDeployer(); -let { randomManager } = findRandomManager(deploy_epoch); +// let { randomManager } = findRandomManager(deploy_epoch); -await compileRandomManager(deploy_epoch); +// await compileRandomManager(deploy_epoch); -let rmStoreManager: RandomManagerManager = getRMStoreManager(deploy_epoch); +// let rmStoreManager: RandomManagerManager = getRMStoreManager(deploy_epoch); -const { witness: commitRoundWitness } = rmStoreManager.getCommitWitness(+round); -const { witness: resultRoundWitness } = rmStoreManager.getResultWitness(+round); -const commitValue = rmStoreManager.commits[+round]; -const vrfValue = randomManager.curRandomValue.get(); +// const { witness: commitRoundWitness } = rmStoreManager.getCommitWitness(+round); +// const { witness: resultRoundWitness } = rmStoreManager.getResultWitness(+round); +// const commitValue = rmStoreManager.commits[+round]; +// const vrfValue = randomManager.curRandomValue.get(); -let tx = await Mina.transaction(deployer, async () => { - await randomManager.reveal( - commitValue, - commitRoundWitness, - resultRoundWitness - ); -}); +// let tx = await Mina.transaction(deployer, async () => { +// await randomManager.reveal( +// commitValue, +// commitRoundWitness, +// resultRoundWitness +// ); +// }); -await tx.prove(); -await tx.sign([deployerKey]).send(); +// await tx.prove(); +// await tx.sign([deployerKey]).send(); -rmStoreManager.addResultValue( - +round, - Poseidon.hash([commitValue.value, vrfValue]) -); +// rmStoreManager.addResultValue( +// +round, +// Poseidon.hash([commitValue.value, vrfValue]) +// ); -storeRMStoreManager(rmStoreManager, deploy_epoch); +// storeRMStoreManager(rmStoreManager, deploy_epoch); diff --git a/src/Factory.ts b/src/Factory.ts index 9c566a6..fe94dc3 100644 --- a/src/Factory.ts +++ b/src/Factory.ts @@ -16,13 +16,13 @@ import { Cache, UInt32, } from 'o1js'; -import { BLOCK_PER_ROUND } from './constants'; -import { MerkleMap20 } from './Structs/CustomMerkleMap'; -import { RandomManager } from './Random/RandomManager'; -import { PLottery } from './PLottery'; +import { BLOCK_PER_ROUND } from './constants.js'; +import { MerkleMap20 } from './Structs/CustomMerkleMap.js'; +import { RandomManager } from './Random/RandomManager.js'; +import { PLottery } from './PLottery.js'; import { ZkonRequestCoordinator, ZkonZkProgram } from 'zkon-zkapp'; -import { TicketReduceProgram } from './Proofs/TicketReduceProof'; -import { DistributionProgram } from './Proofs/DistributionProof'; +import { TicketReduceProgram } from './Proofs/TicketReduceProof.js'; +import { DistributionProgram } from './Proofs/DistributionProof.js'; const emptyMerkleMapRoot = new MerkleMap().getRoot(); @@ -46,7 +46,7 @@ export class DeployEvent extends Struct({ plottery: PublicKey, }) {} -const startSlot = Field(0); +const startSlot = Field(87117); // Current slot on devnet ///Just copy with other vk for random manager export class PlotteryFactory extends SmartContract { diff --git a/src/StateManager/FactoryStateManager.ts b/src/StateManager/FactoryStateManager.ts index 98bd873..4d2b45d 100644 --- a/src/StateManager/FactoryStateManager.ts +++ b/src/StateManager/FactoryStateManager.ts @@ -1,7 +1,7 @@ import { Field, MerkleMap, PublicKey } from 'o1js'; -import { PStateManager } from './PStateManager'; -import { PLottery } from '../PLottery'; -import { RandomManagerManager } from './RandomManagerManager'; +import { PStateManager } from './PStateManager.js'; +import { PLottery } from '../PLottery.js'; +import { RandomManagerManager } from './RandomManagerManager.js'; interface IDeployInfo { round: number; diff --git a/src/StateManager/RandomManagerManager.ts b/src/StateManager/RandomManagerManager.ts index 0c11842..67f6131 100644 --- a/src/StateManager/RandomManagerManager.ts +++ b/src/StateManager/RandomManagerManager.ts @@ -1,7 +1,7 @@ /// Best fucking naming import { Field, MerkleMap, MerkleMapWitness } from 'o1js'; -import { CommitValue, RandomManager } from '../Random/RandomManager'; +import { CommitValue, RandomManager } from '../Random/RandomManager.js'; interface WitnessedValue { value: Field; From fdcee1ccb6034963d938337d84e2f97eef2ca6e1 Mon Sep 17 00:00:00 2001 From: aii23 Date: Wed, 9 Oct 2024 16:09:44 +0700 Subject: [PATCH 5/6] Added buy ticket script --- scripts/deploy_1rnd_pottery.ts | 27 +--- scripts/pbuy_ticket.ts | 188 +++++++++-------------- scripts/utils.ts | 24 +++ src/Factory.ts | 8 +- src/Random/RandomManager.ts | 4 +- src/StateManager/FactoryStateManager.ts | 6 +- src/StateManager/RandomManagerManager.ts | 27 ++-- src/Tests/Random.test.ts | 2 +- src/constants.ts | 2 +- src/util.ts | 20 +++ 10 files changed, 149 insertions(+), 159 deletions(-) diff --git a/scripts/deploy_1rnd_pottery.ts b/scripts/deploy_1rnd_pottery.ts index b1cb824..41ff944 100644 --- a/scripts/deploy_1rnd_pottery.ts +++ b/scripts/deploy_1rnd_pottery.ts @@ -1,7 +1,7 @@ -import { Field, Mina, PrivateKey, PublicKey } from 'o1js'; +import { AccountUpdate, Field, Mina, PrivateKey, PublicKey } from 'o1js'; import { DeployEvent, PlotteryFactory } from '../src/Factory.js'; import { FactoryManager } from '../src/StateManager/FactoryStateManager.js'; -import { configDefaultInstance } from './utils.js'; +import { configDefaultInstance, getFedFactoryManager } from './utils.js'; import * as fs from 'fs'; let { transactionFee } = configDefaultInstance(); @@ -25,8 +25,6 @@ const networkId = Mina.activeInstance.getNetworkId().toString(); let { verificationKey } = await PlotteryFactory.compile(); -const factoryManager = new FactoryManager(); - let factoryAddress: PublicKey; if ( @@ -47,8 +45,6 @@ if (fs.existsSync(factoryDataPath)) { let factory = new PlotteryFactory(factoryAddress); -let factoryEvents = await factory.fetchEvents(); - let deployments; const deploymentsPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/deployments.json`; @@ -60,18 +56,7 @@ if (fs.existsSync(deploymentsPath)) { deployments = {}; } -// Restore state of factoryManager -for (const event of factoryEvents) { - let deployEvent = event.event.data as any; - - console.log('event'); - console.log(deployEvent); - factoryManager.addDeploy( - +deployEvent.round, - deployEvent.randomManager, - deployEvent.plottery - ); -} +const factoryManager = await getFedFactoryManager(factory); for (let round = +from; round <= +to; round++) { if (factoryManager.roundsMap.get(Field(round)).greaterThan(0).toBoolean()) { @@ -92,6 +77,9 @@ for (let round = +from; round <= +to; round++) { let tx = Mina.transaction( { sender: deployer, fee: 5 * transactionFee }, async () => { + AccountUpdate.fundNewAccount(deployer); + AccountUpdate.fundNewAccount(deployer); + await factory.deployRound(witness, randomManagerAddress, plotteryAddress); } ); @@ -105,9 +93,8 @@ for (let round = +from; round <= +to; round++) { if (txResult.status === 'rejected') { console.log(`Transaction failed due to following reason`); - console.log(txResult.toPretty()); console.log(txResult.errors); - continue; + break; } factoryManager.addDeploy(round, randomManagerAddress, plotteryAddress); diff --git a/scripts/pbuy_ticket.ts b/scripts/pbuy_ticket.ts index 4d4bba0..5392e25 100644 --- a/scripts/pbuy_ticket.ts +++ b/scripts/pbuy_ticket.ts @@ -1,118 +1,70 @@ -// /** -// * This script can be used to interact with the Add contract, after deploying it. -// * -// * We call the update() method on the contract, create a proof and send it to the chain. -// * The endpoint that we interact with is read from your config.json. -// * -// * This simulates a user interacting with the zkApp from a browser, except that here, sending the transaction happens -// * from the script and we're using your pre-funded zkApp account to pay the transaction fee. In a real web app, the user's wallet -// * would send the transaction and pay the fee. -// * -// * To run locally: -// * Build the project: `$ npm run build` -// * Run with node: `$ node build/scripts/buy_ticket.js `. -// */ -// import fs from 'fs/promises'; -// import { Cache, Field, Mina, NetworkId, PrivateKey, fetchAccount } from 'o1js'; -// import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; -// import { Ticket } from '../src/Structs/Ticket.js'; -// import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; -// import { PStateManager } from '../src/StateManager/PStateManager.js'; -// import { findPlottery } from './utils.js'; - -// // check command line arg -// let deployAlias = process.argv[2]; -// if (!deployAlias) -// throw Error(`Missing argument. - -// Usage: -// node build/src/interact.js -// `); -// Error.stackTraceLimit = 1000; -// const DEFAULT_NETWORK_ID = 'testnet'; - -// // parse config and private key from file -// type Config = { -// deployAliases: Record< -// string, -// { -// networkId?: string; -// url: string; -// keyPath: string; -// fee: string; -// feepayerKeyPath: string; -// feepayerAlias: string; -// } -// >; -// }; -// let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); -// let config = configJson.deployAliases[deployAlias]; -// let feepayerKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( -// await fs.readFile(config.feepayerKeyPath, 'utf8') -// ); - -// let zkAppKeysBase58: { privateKey: string; publicKey: string } = JSON.parse( -// await fs.readFile(config.keyPath, 'utf8') -// ); - -// let feepayerKey = PrivateKey.fromBase58(feepayerKeysBase58.privateKey); -// let zkAppKey = PrivateKey.fromBase58(zkAppKeysBase58.privateKey); - -// // set up Mina instance and contract we interact with -// const Network = Mina.Network({ -// // We need to default to the testnet networkId if none is specified for this deploy alias in config.json -// // This is to ensure the backward compatibility. -// networkId: (config.networkId ?? DEFAULT_NETWORK_ID) as NetworkId, -// mina: config.url, -// }); -// // const Network = Mina.Network(config.url); -// const fee = Number(config.fee) * 1e9; // in nanomina (1 billion = 1.0 mina) -// Mina.setActiveInstance(Network); -// let feepayerAddress = feepayerKey.toPublicKey(); -// let zkAppAddress = zkAppKey.toPublicKey(); - -// let { plottery: lottery, PLottery } = findPlottery(); - -// // compile the contract to create prover keys -// console.log('compile the DP'); -// await DistributionProgram.compile(); -// console.log('compile reduce proof'); -// await TicketReduceProgram.compile(); -// console.log('compile the Lottery'); -// let lotteryCompileResult = await PLottery.compile(); -// // let mockLotteryCompileResult = await MockLottery.compile({ -// // cache: Cache.FileSystem('../cache'), -// // }); - -// console.log(`Fetch: ${zkAppAddress.toBase58()}`); -// console.log(`Onchain VK: `, lottery.account.verificationKey); -// console.log( -// `Local VK1: `, -// lotteryCompileResult.verificationKey.hash.toString() -// ); -// // console.log( -// // `Local VK2: `, -// // mockLotteryCompileResult.verificationKey.hash.toString() -// // ); -// await fetchAccount({ publicKey: zkAppAddress }); -// await fetchAccount({ -// publicKey: 'B62qnBkcyABfjz2cqJPzNZKjVt9M9kx1vgoiWLbkJUnk16Cz8KX8qC4', -// }); - -// console.log(lottery.bankRoot.get().toString()); - -// console.log(lottery.ticketRoot.get().toString()); - -// const state = new PStateManager(lottery, lottery.startBlock.get().value); - -// const ticket = Ticket.from([1, 1, 1, 1, 1, 1], feepayerAddress, 1); - -// // console.log(`Digest: `, await MockLottery.digest()); - -// let tx = await Mina.transaction({ sender: feepayerAddress, fee }, async () => { -// await lottery.buyTicket(ticket, Field(0)); -// }); -// await tx.prove(); -// let txResult = await tx.sign([feepayerKey]).send(); - -// console.log(`Tx successful. Hash: `, txResult.hash); +import fs from 'fs'; +import { + Cache, + Field, + Mina, + NetworkId, + PrivateKey, + UInt32, + fetchAccount, +} from 'o1js'; +import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; +import { Ticket } from '../src/Structs/Ticket.js'; +import { TicketReduceProgram } from '../src/Proofs/TicketReduceProof.js'; +import { PStateManager } from '../src/StateManager/PStateManager.js'; +import { configDefaultInstance, getFedFactoryManager } from './utils.js'; +import { PlotteryFactory } from '../src/Factory.js'; +import { BLOCK_PER_ROUND } from '../src/constants.js'; +import { PLottery } from '../src/PLottery.js'; + +const { transactionFee } = configDefaultInstance(); + +const networkId = Mina.activeInstance.getNetworkId().toString(); +const { verificationKey } = await PlotteryFactory.compile(); + +const deployerKey = PrivateKey.fromBase58(process.env.DEPLOYER_KEY!); +const deployer = deployerKey.toPublicKey(); + +// Get factory +const factoryDataPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/factory.json`; + +const factoryAddress = JSON.parse( + fs.readFileSync(factoryDataPath).toString() +).address; + +const factory = new PlotteryFactory(factoryAddress); +const startSlot = factory.startSlot.get(); +const currentSlot = Mina.currentSlot(); +const currentRound = currentSlot.sub(startSlot).div(BLOCK_PER_ROUND); + +const factoryManager = await getFedFactoryManager(factory); + +const ticket = Ticket.from([1, 1, 1, 1, 1, 1], deployer, 1); + +const plottery = factoryManager.plotteryManagers[+currentRound].contract; + +// compile the contract to create prover keys +console.log('compile the DP'); +await DistributionProgram.compile({ cache: Cache.FileSystem('../cache') }); +console.log('compile reduce proof'); +await TicketReduceProgram.compile({ cache: Cache.FileSystem('../cache') }); +console.log('compile the Lottery'); +await PLottery.compile({ + cache: Cache.FileSystem('../cache'), +}); + +await fetchAccount({ publicKey: plottery.address }); +await fetchAccount({ + publicKey: deployer, +}); + +let tx = await Mina.transaction( + { sender: deployer, fee: transactionFee }, + async () => { + await plottery.buyTicket(ticket); + } +); +await tx.prove(); +let txResult = await tx.sign([deployerKey]).send(); + +console.log(`Tx successful. Hash: `, txResult.hash); diff --git a/scripts/utils.ts b/scripts/utils.ts index 8ebf3b5..16668e4 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -14,6 +14,8 @@ import { import { DistributionProgram } from '../src/Proofs/DistributionProof.js'; import { ZkOnCoordinatorAddress } from '../src/constants.js'; import { RandomManagerManager } from '../src/StateManager/RandomManagerManager.js'; +import { FactoryManager } from '../src/StateManager/FactoryStateManager.js'; +import { PlotteryFactory } from '../src/Factory.js'; export const configDefaultInstance = (): { transactionFee: number } => { const transactionFee = 100_000_000; @@ -162,6 +164,28 @@ export const getDeployer = (): { }; */ +export const getFedFactoryManager = async ( + factory: PlotteryFactory +): Promise => { + const factoryManager = new FactoryManager(); + + const factoryEvents = await factory.fetchEvents(); + + for (const event of factoryEvents) { + const deployEvent = event.event.data as any; + + console.log('event'); + console.log(deployEvent); + factoryManager.addDeploy( + +deployEvent.round, + deployEvent.randomManager, + deployEvent.plottery + ); + } + + return factoryManager; +}; + export const getIPFSCID = (): { hashPart1: Field; hashPart2: Field } => { function segmentHash(ipfsHashFile: string) { const ipfsHash0 = ipfsHashFile.slice(0, 30); // first part of the ipfsHash diff --git a/src/Factory.ts b/src/Factory.ts index fe94dc3..cfb7080 100644 --- a/src/Factory.ts +++ b/src/Factory.ts @@ -46,8 +46,6 @@ export class DeployEvent extends Struct({ plottery: PublicKey, }) {} -const startSlot = Field(87117); // Current slot on devnet - ///Just copy with other vk for random manager export class PlotteryFactory extends SmartContract { events = { @@ -55,10 +53,13 @@ export class PlotteryFactory extends SmartContract { }; @state(Field) roundsRoot = State(); + @state(UInt32) startSlot = State(); init() { super.init(); this.roundsRoot.set(emptyMerkleMapRoot); + this.network.globalSlotSinceGenesis.requireNothing(); + this.startSlot.set(this.network.globalSlotSinceGenesis.get()); } @method @@ -75,7 +76,8 @@ export class PlotteryFactory extends SmartContract { const [newRoot] = witness.computeRootAndKeyV2(Field(1)); this.roundsRoot.set(newRoot); - const localStartSlot = startSlot.add(round.mul(BLOCK_PER_ROUND)); + const startSlot = this.startSlot.getAndRequireEquals(); + const localStartSlot = startSlot.value.add(round.mul(BLOCK_PER_ROUND)); // Deploy and initialize random manager { diff --git a/src/Random/RandomManager.ts b/src/Random/RandomManager.ts index 157dd68..7fd5051 100644 --- a/src/Random/RandomManager.ts +++ b/src/Random/RandomManager.ts @@ -15,14 +15,12 @@ import { state, } from 'o1js'; import { BLOCK_PER_ROUND, ZkOnCoordinatorAddress } from '../constants.js'; -import { convertToUInt32 } from '../util.js'; - import { ZkonZkProgram, ZkonRequestCoordinator, ExternalRequestEvent, } from 'zkon-zkapp'; -import { getIPFSCID } from '../../scripts/utils.js'; +import { getIPFSCID } from '../util.js'; const emptyMapRoot = new MerkleMap().getRoot(); diff --git a/src/StateManager/FactoryStateManager.ts b/src/StateManager/FactoryStateManager.ts index 4d2b45d..b280001 100644 --- a/src/StateManager/FactoryStateManager.ts +++ b/src/StateManager/FactoryStateManager.ts @@ -2,6 +2,7 @@ import { Field, MerkleMap, PublicKey } from 'o1js'; import { PStateManager } from './PStateManager.js'; import { PLottery } from '../PLottery.js'; import { RandomManagerManager } from './RandomManagerManager.js'; +import { RandomManager } from '../Random/RandomManager.js'; interface IDeployInfo { round: number; @@ -35,12 +36,15 @@ export class FactoryManager { this.roundsMap.set(Field(round), Field(1)); const plotteryContract = new PLottery(plottery); + const randomManagerContract = new RandomManager(randomManager); this.plotteryManagers[round] = new PStateManager( plotteryContract, this.isMock, this.shouldUpdateState ); - this.randomManagers[round] = new RandomManagerManager(); + this.randomManagers[round] = new RandomManagerManager( + randomManagerContract + ); } } diff --git a/src/StateManager/RandomManagerManager.ts b/src/StateManager/RandomManagerManager.ts index 67f6131..6412954 100644 --- a/src/StateManager/RandomManagerManager.ts +++ b/src/StateManager/RandomManagerManager.ts @@ -10,8 +10,11 @@ interface WitnessedValue { export class RandomManagerManager { commit: CommitValue | undefined; + contract: RandomManager; - constructor() {} + constructor(contract: RandomManager) { + this.contract = contract; + } addCommit(commit: CommitValue) { if (this.commit) { @@ -32,18 +35,18 @@ export class RandomManagerManager { return JSON.stringify(json); } - static fromJSON(s: string): RandomManagerManager { - const data = JSON.parse(s); + // static fromJSON(s: string): RandomManagerManager { + // const data = JSON.parse(s); - const res = new RandomManagerManager(); + // const res = new RandomManagerManager(); - res.addCommit( - new CommitValue({ - value: Field(data.commit.value), - salt: Field(data.commit.salt), - }) - ); + // res.addCommit( + // new CommitValue({ + // value: Field(data.commit.value), + // salt: Field(data.commit.salt), + // }) + // ); - return res; - } + // return res; + // } } diff --git a/src/Tests/Random.test.ts b/src/Tests/Random.test.ts index f501d13..5fd667f 100644 --- a/src/Tests/Random.test.ts +++ b/src/Tests/Random.test.ts @@ -50,7 +50,7 @@ describe('Add', () => { randomManagerPrivateKey: PrivateKey, factoryAddress: PublicKey, factoryPrivateKey: PrivateKey, - factory: PlotteryFactory, + factory: MockedPlotteryFactory, randomManager: MockedRandomManager, factoryManager: FactoryManager, mineNBlocks: (n: number) => void, diff --git a/src/constants.ts b/src/constants.ts index 92e1b32..c9310a2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,7 +3,7 @@ import { PublicKey, UInt64 } from 'o1js'; export const NUMBERS_IN_TICKET = 6; export const TICKET_PRICE = UInt64.from(10 * 10 ** 9); -export const BLOCK_PER_ROUND = 480; // Approximate blocks per 24 hours +export const BLOCK_PER_ROUND = 20; // Approximate blocks per 1 hour export const SCORE_COEFFICIENTS = [0, 90, 324, 2187, 26244, 590490, 31886460]; // Should be updated with appropriate probability diff --git a/src/util.ts b/src/util.ts index a504da5..cbbb37a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,6 +1,9 @@ +import * as fs from 'fs'; + import { Field, Gadgets, MerkleMap, Poseidon, UInt32, UInt64 } from 'o1js'; import { MerkleMap20 } from './Structs/CustomMerkleMap.js'; import { PackedUInt32Factory } from 'o1js-pack'; +import { StringCircuitValue } from 'zkon-zkapp'; export class NumberPacked extends PackedUInt32Factory() {} @@ -17,3 +20,20 @@ export function convertToUInt32(value: Field): UInt32 { return val; } + +export const getIPFSCID = (): { hashPart1: Field; hashPart2: Field } => { + function segmentHash(ipfsHashFile: string) { + const ipfsHash0 = ipfsHashFile.slice(0, 30); // first part of the ipfsHash + const ipfsHash1 = ipfsHashFile.slice(30); // second part of the ipfsHash + + const hashPart1 = new StringCircuitValue(ipfsHash0).toField(); + + const hashPart2 = new StringCircuitValue(ipfsHash1).toField(); + + return { hashPart1, hashPart2 }; + } + + let cidBuffer = fs.readFileSync('./random_request_cid'); + + return segmentHash(cidBuffer.toString()); +}; From d56737f485bce752a1624b6e426fc80208f78d51 Mon Sep 17 00:00:00 2001 From: aii23 Date: Wed, 9 Oct 2024 18:34:04 +0700 Subject: [PATCH 6/6] Changed random API path --- .gitignore | 3 +++ random_request_cid | 2 +- random_request_file | 6 +++--- scripts/pbuy_ticket.ts | 41 +++++++++++++++++++++++++++++++++---- scripts/publish_request.ts | 35 ++++++++++++++++++------------- src/Random/RandomManager.ts | 10 ++++++--- src/index.ts | 6 +++++- 7 files changed, 77 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 6137df5..ee005e7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ coverage # System .DS_Store +# Deploy info. Consist private keys and other sensitive data. +deployV2 + # Never commit keys to Git! keys diff --git a/random_request_cid b/random_request_cid index a0721c6..c13f9ac 100644 --- a/random_request_cid +++ b/random_request_cid @@ -1 +1 @@ -bafkreif2ett25ddjcevhnmaxmimkjdoigtsaj6bfyfil5gu65l2r6luxqm \ No newline at end of file +bafkreicnannsz4gqqk3ccfwfd3z2hfzonr63z3hgsplefgrmz37psay23y \ No newline at end of file diff --git a/random_request_file b/random_request_file index e76e99e..6f08bcd 100644 --- a/random_request_file +++ b/random_request_file @@ -1,6 +1,6 @@ { "method": "GET", - "baseURL": "https://quantum-random.com/quantum", - "path": "seed", - "zkapp": "var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nvar __metadata = (this && this.__metadata) || function (k, v) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(k, v);\n};\nimport { Field, MerkleMap, MerkleMapWitness, Poseidon, PublicKey, SmartContract, State, Struct, UInt32, ZkProgram, method, state, } from 'o1js';\nimport { BLOCK_PER_ROUND, ZkOnCoordinatorAddress } from '../constants.js';\nimport { convertToUInt32 } from '../util.js';\nimport { ZkonZkProgram, ZkonRequestCoordinator, ExternalRequestEvent, } from 'zkon-zkapp';\nimport { getIPFSCID } from '../../scripts/utils.js';\nconst emptyMapRoot = new MerkleMap().getRoot();\nexport let ZkonProof_ = ZkProgram.Proof(ZkonZkProgram);\nexport class ZkonProof extends ZkonProof_ {\n}\nexport class CommitValue extends Struct({\n value: Field,\n salt: Field,\n}) {\n hash() {\n return Poseidon.hash([this.value, this.salt]);\n }\n}\nconst { hashPart1, hashPart2 } = getIPFSCID();\n// Add events\nexport function getRandomManager(owner, coordinatorAddress = ZkOnCoordinatorAddress) {\n class RandomManager extends SmartContract {\n constructor() {\n super(...arguments);\n this.commitRoot = State();\n this.resultRoot = State();\n this.curRandomValue = State();\n this.startSlot = State();\n this.events = {\n requested: ExternalRequestEvent,\n };\n }\n init() {\n super.init();\n this.commitRoot.set(emptyMapRoot);\n this.resultRoot.set(emptyMapRoot);\n }\n /**\n * @notice Inital set of start slot.\n * @dev It should be equal to startBlock on PLottery. Called only once.\n *\n * @param startSlot start slot value.\n *\n */\n async setStartSlot(startSlot) {\n this.permissionCheck();\n this.startSlot.getAndRequireEquals().assertEquals(UInt32.from(0));\n this.startSlot.set(startSlot);\n }\n /**\n * @notice Commit hidden value.\n * @dev Only hash o value and salt is stored. So value is hidden.\n *\n * @param commitValue Commit value = value + slot.\n * @param commitWitness Witness of commit tree.\n *\n */\n async commit(commitValue, commitWitness) {\n this.permissionCheck();\n const [prevCommitRoot, round] = commitWitness.computeRootAndKey(Field(0));\n this.checkRoundDoNotEnd(convertToUInt32(round));\n this.commitRoot\n .getAndRequireEquals()\n .assertEquals(prevCommitRoot, 'commit: Wrong commit witness');\n const [newCommitRoot] = commitWitness.computeRootAndKey(commitValue.hash());\n this.commitRoot.set(newCommitRoot);\n }\n /**\n * @notice Reveal number commited previously.\n * @dev This function can be called only after oracle provided its random value\n *\n * @param commitValue Commit value = value + slot.\n * @param commitWitness Witness of commit tree.\n * @param resultWitness Witness of result tree.\n *\n */\n async reveal(commitValue, commitWitness, resultWitness) {\n this.permissionCheck();\n // Check VRF computed\n const curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertGreaterThan(Field(0), 'reveal: No random value in stash');\n // Check commit witness\n const [prevCommitRoot, round] = commitWitness.computeRootAndKey(commitValue.hash());\n this.commitRoot\n .getAndRequireEquals()\n .assertEquals(prevCommitRoot, 'reveal: Wrong commit witness');\n // Check result witness\n const [prevResultRoot, resultRound] = resultWitness.computeRootAndKey(Field(0));\n this.resultRoot\n .getAndRequireEquals()\n .assertEquals(prevResultRoot, 'reveal: wrong result witness');\n round.assertEquals(resultRound, 'reveal: Round for commit and result should be equal');\n // Check round is over\n this.checkRoundPass(convertToUInt32(round));\n // Compute result\n const resultValue = Poseidon.hash([commitValue.value, curRandomValue]);\n // Update result\n const [newResultRoot] = resultWitness.computeRootAndKey(resultValue);\n this.resultRoot.set(newResultRoot);\n // Consume random value\n this.curRandomValue.set(Field(0));\n }\n /**\n * @notice Sends request to ZKOn oracle.\n * @dev Request body is stored on IPFS.\n *\n */\n async callZkon() {\n let curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertEquals(Field(0), 'receiveZkonResponse: prev random value was not consumed. Call reveal first');\n const coordinator = new ZkonRequestCoordinator(coordinatorAddress);\n const requestId = await coordinator.sendRequest(this.address, hashPart1, hashPart2);\n const event = new ExternalRequestEvent({\n id: requestId,\n hash1: hashPart1,\n hash2: hashPart2,\n });\n this.emitEvent('requested', event);\n }\n /**\n * @notice Callback function for ZKOn response\n *\n */\n async receiveZkonResponse(requestId, proof) {\n let curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertEquals(Field(0), 'receiveZkonResponse: prev random value was not consumed. Call reveal first');\n const coordinator = new ZkonRequestCoordinator(coordinatorAddress);\n await coordinator.recordRequestFullfillment(requestId, proof);\n this.curRandomValue.set(proof.publicInput.dataField);\n }\n /**\n * @notice Checks that sender is the owner of the contract.\n *\n */\n permissionCheck() {\n this.sender.getAndRequireSignature().assertEquals(owner);\n }\n /**\n * @notice Checks that specified round have already passed.\n *\n * @param round Round to check\n */\n checkRoundPass(round) {\n const startBlock = this.startSlot.getAndRequireEquals();\n this.network.globalSlotSinceGenesis.requireBetween(startBlock.add(round.add(1).mul(BLOCK_PER_ROUND)), UInt32.MAXINT());\n }\n /**\n * @notice Checks that round have not ended yet\n *\n * @param round Round to check\n */\n checkRoundDoNotEnd(round) {\n const startBlock = this.startSlot.getAndRequireEquals();\n this.network.globalSlotSinceGenesis.requireBetween(UInt32.from(0), startBlock.add(round.add(1).mul(BLOCK_PER_ROUND)));\n }\n }\n __decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n ], RandomManager.prototype, \"commitRoot\", void 0);\n __decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n ], RandomManager.prototype, \"resultRoot\", void 0);\n __decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n ], RandomManager.prototype, \"curRandomValue\", void 0);\n __decorate([\n state(UInt32),\n __metadata(\"design:type\", Object)\n ], RandomManager.prototype, \"startSlot\", void 0);\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [UInt32]),\n __metadata(\"design:returntype\", Promise)\n ], RandomManager.prototype, \"setStartSlot\", null);\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [CommitValue,\n MerkleMapWitness]),\n __metadata(\"design:returntype\", Promise)\n ], RandomManager.prototype, \"commit\", null);\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [CommitValue,\n MerkleMapWitness,\n MerkleMapWitness]),\n __metadata(\"design:returntype\", Promise)\n ], RandomManager.prototype, \"reveal\", null);\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", []),\n __metadata(\"design:returntype\", Promise)\n ], RandomManager.prototype, \"callZkon\", null);\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [Field, ZkonProof]),\n __metadata(\"design:returntype\", Promise)\n ], RandomManager.prototype, \"receiveZkonResponse\", null);\n return RandomManager;\n}\nexport function getMockedRandomManager(owner) {\n class MockedRandomManager extends getRandomManager(owner, PublicKey.empty()) {\n async mockReceiveZkonResponse(newValue) {\n this.curRandomValue.set(newValue);\n }\n }\n __decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [Field]),\n __metadata(\"design:returntype\", Promise)\n ], MockedRandomManager.prototype, \"mockReceiveZkonResponse\", null);\n return MockedRandomManager;\n}\n//# sourceMappingURL=RandomManager.js.map" + "baseURL": "https://random-data-api.com/api/number/random_number", + "path": "number", + "zkapp": "var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n};\nvar __metadata = (this && this.__metadata) || function (k, v) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(k, v);\n};\nimport { Field, MerkleMap, Poseidon, PublicKey, SmartContract, State, Struct, UInt32, ZkProgram, method, state, } from 'o1js';\nimport { BLOCK_PER_ROUND, ZkOnCoordinatorAddress } from '../constants.js';\nimport { ZkonZkProgram, ZkonRequestCoordinator, ExternalRequestEvent, } from 'zkon-zkapp';\nimport { getIPFSCID } from '../util.js';\nconst emptyMapRoot = new MerkleMap().getRoot();\nexport let ZkonProof_ = ZkProgram.Proof(ZkonZkProgram);\nexport class ZkonProof extends ZkonProof_ {\n}\nexport class CommitValue extends Struct({\n value: Field,\n salt: Field,\n}) {\n hash() {\n return Poseidon.hash([this.value, this.salt]);\n }\n}\nconst { hashPart1, hashPart2 } = getIPFSCID();\nconst coordinatorAddress = ZkOnCoordinatorAddress;\nconst owner = PublicKey.fromBase58('B62qjGsPY47SMkTykivPBAU3riS9gvMMrGr7ve6ynoHJNBzAhQmtoBn');\nexport class RandomManager extends SmartContract {\n constructor() {\n super(...arguments);\n this.startSlot = State();\n this.commit = State();\n this.result = State();\n this.curRandomValue = State();\n this.events = {\n requested: ExternalRequestEvent,\n };\n }\n // init() {\n // super.init();\n // // assert(\n // // Bool(false),\n // // 'This contract is supposed to be deployed from factory. No init call there'\n // // );\n // }\n /**\n * @notice Commit hidden value.\n * @dev Only hash o value and salt is stored. So value is hidden.\n *\n * @param commitValue Commit value = value + slot.\n *\n */\n async commitValue(commitValue) {\n this.permissionCheck();\n const currentCommit = this.commit.getAndRequireEquals();\n currentCommit.assertEquals(Field(0), 'Already committed');\n this.commit.set(commitValue.hash());\n await this.callZkon();\n }\n /*\n \n /**\n * @notice Reveal number committed previously.\n * @dev This function can be called only after oracle provided its random value\n *\n * @param commitValue Commit value = value + slot.\n *\n */\n async reveal(commitValue) {\n this.permissionCheck();\n const result = this.result.getAndRequireEquals();\n result.assertEquals(Field(0), 'reveal: Result already computed');\n // Check VRF computed\n const curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertGreaterThan(Field(0), 'reveal: No random value');\n // Check commit\n const commit = this.commit.getAndRequireEquals();\n commit.assertEquals(commitValue.hash(), 'reveal: wrong commit value');\n // Check round is over\n this.checkRoundPass();\n // Compute result\n const resultValue = Poseidon.hash([commitValue.value, curRandomValue]);\n // Update result\n this.result.set(resultValue);\n }\n /**\n * @notice Sends request to ZKOn oracle.\n * @dev Request body is stored on IPFS.\n *\n */\n async callZkon() {\n let curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertEquals(Field(0), 'random value have already been computed');\n const coordinator = new ZkonRequestCoordinator(coordinatorAddress);\n const requestId = await coordinator.sendRequest(this.address, hashPart1, hashPart2);\n const event = new ExternalRequestEvent({\n id: requestId,\n hash1: hashPart1,\n hash2: hashPart2,\n });\n this.emitEvent('requested', event);\n }\n /**\n * @notice Callback function for ZKOn response\n *\n */\n async receiveZkonResponse(requestId, proof) {\n let curRandomValue = this.curRandomValue.getAndRequireEquals();\n curRandomValue.assertEquals(Field(0), 'receiveZkonResponse: prev random value was not consumed. Call reveal first');\n const coordinator = new ZkonRequestCoordinator(coordinatorAddress);\n await coordinator.recordRequestFullfillment(requestId, proof);\n this.curRandomValue.set(proof.publicInput.dataField);\n }\n /**\n * @notice Checks that sender is the owner of the contract.\n *\n */\n permissionCheck() {\n this.sender.getAndRequireSignature().assertEquals(owner);\n }\n /**\n * @notice Checks that specified round have already passed.\n *\n * @param round Round to check\n */\n checkRoundPass() {\n const startSlot = this.startSlot.getAndRequireEquals();\n this.network.globalSlotSinceGenesis.requireBetween(startSlot.add(BLOCK_PER_ROUND), UInt32.MAXINT());\n }\n}\n__decorate([\n state(UInt32),\n __metadata(\"design:type\", Object)\n], RandomManager.prototype, \"startSlot\", void 0);\n__decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n], RandomManager.prototype, \"commit\", void 0);\n__decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n], RandomManager.prototype, \"result\", void 0);\n__decorate([\n state(Field),\n __metadata(\"design:type\", Object)\n], RandomManager.prototype, \"curRandomValue\", void 0);\n__decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [CommitValue]),\n __metadata(\"design:returntype\", Promise)\n], RandomManager.prototype, \"commitValue\", null);\n__decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [CommitValue]),\n __metadata(\"design:returntype\", Promise)\n], RandomManager.prototype, \"reveal\", null);\n__decorate([\n method,\n __metadata(\"design:type\", Function),\n __metadata(\"design:paramtypes\", [Field, ZkonProof]),\n __metadata(\"design:returntype\", Promise)\n], RandomManager.prototype, \"receiveZkonResponse\", null);\n//# sourceMappingURL=RandomManager.js.map" } \ No newline at end of file diff --git a/scripts/pbuy_ticket.ts b/scripts/pbuy_ticket.ts index 5392e25..f674c9b 100644 --- a/scripts/pbuy_ticket.ts +++ b/scripts/pbuy_ticket.ts @@ -5,6 +5,7 @@ import { Mina, NetworkId, PrivateKey, + PublicKey, UInt32, fetchAccount, } from 'o1js'; @@ -16,6 +17,7 @@ import { configDefaultInstance, getFedFactoryManager } from './utils.js'; import { PlotteryFactory } from '../src/Factory.js'; import { BLOCK_PER_ROUND } from '../src/constants.js'; import { PLottery } from '../src/PLottery.js'; +import axios from 'axios'; const { transactionFee } = configDefaultInstance(); @@ -28,13 +30,44 @@ const deployer = deployerKey.toPublicKey(); // Get factory const factoryDataPath = `./deployV2/${networkId}/${verificationKey.hash.toString()}/factory.json`; -const factoryAddress = JSON.parse( - fs.readFileSync(factoryDataPath).toString() -).address; +const factoryAddress = PublicKey.fromBase58( + JSON.parse(fs.readFileSync(factoryDataPath).toString()).address +); + +console.log(factoryAddress.toBase58()); + +await fetchAccount({ publicKey: factoryAddress }); const factory = new PlotteryFactory(factoryAddress); const startSlot = factory.startSlot.get(); -const currentSlot = Mina.currentSlot(); + +const data = await axios.post( + 'https://api.minascan.io/node/devnet/v1/graphql', + JSON.stringify({ + query: ` + query { + bestChain(maxLength:1) { + protocolState { + consensusState { + blockHeight, + slotSinceGenesis + } + } + } + } +`, + }), + { + headers: { + 'Content-Type': 'application/json', + }, + responseType: 'json', + } +); +const currentSlot = UInt32.from( + data.data.data.bestChain[0].protocolState.consensusState.slotSinceGenesis +); + const currentRound = currentSlot.sub(startSlot).div(BLOCK_PER_ROUND); const factoryManager = await getFedFactoryManager(factory); diff --git a/scripts/publish_request.ts b/scripts/publish_request.ts index 29a714f..88829ea 100644 --- a/scripts/publish_request.ts +++ b/scripts/publish_request.ts @@ -1,17 +1,17 @@ -// import dotenv from 'dotenv'; -// dotenv.config(); +import dotenv from 'dotenv'; +dotenv.config(); -// import { readFileSync, writeFileSync } from 'fs'; -// import { PinataSDK } from 'pinata'; +import { readFileSync, writeFileSync } from 'fs'; +import { PinataSDK } from 'pinata'; -// // bafkreif2ett25ddjcevhnmaxmimkjdoigtsaj6bfyfil5gu65l2r6luxqm +// bafkreif2ett25ddjcevhnmaxmimkjdoigtsaj6bfyfil5gu65l2r6luxqm -// const pinata = new PinataSDK({ -// pinataJwt: process.env.PINATA_JWT!, -// pinataGateway: process.env.PINATA_GATEWAY, -// }); +const pinata = new PinataSDK({ + pinataJwt: process.env.PINATA_JWT!, + pinataGateway: process.env.PINATA_GATEWAY, +}); -// const contractCode = readFileSync('./build/src/Random/RandomManager.js'); +const contractCode = readFileSync('./build/src/Random/RandomManager.js'); // const json = { // method: 'GET', @@ -20,9 +20,16 @@ // zkapp: contractCode.toString(), // }; -// let response = await pinata.upload.json(json); +const json = { + method: 'GET', + baseURL: 'https://random-data-api.com/api/number/random_number', + path: 'number', + zkapp: contractCode.toString(), +}; -// console.log(response.IpfsHash); -// writeFileSync('./random_request_cid', response.IpfsHash.toString()); +let response = await pinata.upload.json(json); -// writeFileSync('./random_request_file', JSON.stringify(json, null, 2)); +console.log(response.IpfsHash); +writeFileSync('./random_request_cid', response.IpfsHash.toString()); + +writeFileSync('./random_request_file', JSON.stringify(json, null, 2)); diff --git a/src/Random/RandomManager.ts b/src/Random/RandomManager.ts index 7fd5051..26d6ede 100644 --- a/src/Random/RandomManager.ts +++ b/src/Random/RandomManager.ts @@ -39,7 +39,9 @@ export class CommitValue extends Struct({ const { hashPart1, hashPart2 } = getIPFSCID(); const coordinatorAddress = ZkOnCoordinatorAddress; -const owner = PublicKey.empty(); // #TODO change with real owner address +const owner = PublicKey.fromBase58( + 'B62qjGsPY47SMkTykivPBAU3riS9gvMMrGr7ve6ynoHJNBzAhQmtoBn' +); export class RandomManager extends SmartContract { @state(UInt32) startSlot = State(); @@ -74,6 +76,8 @@ export class RandomManager extends SmartContract { currentCommit.assertEquals(Field(0), 'Already committed'); this.commit.set(commitValue.hash()); + + await this.callZkon(); } /* @@ -113,7 +117,7 @@ export class RandomManager extends SmartContract { * @dev Request body is stored on IPFS. * */ - @method async callZkon() { + public async callZkon() { let curRandomValue = this.curRandomValue.getAndRequireEquals(); curRandomValue.assertEquals( Field(0), @@ -159,7 +163,7 @@ export class RandomManager extends SmartContract { * */ public permissionCheck() { - // this.sender.getAndRequireSignature().assertEquals(owner); + this.sender.getAndRequireSignature().assertEquals(owner); } /** diff --git a/src/index.ts b/src/index.ts index cd7acac..e10784f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,14 @@ export * from './PLottery.js'; +// export * from './Factory.js'; +// export * from './Random/RandomManager.js'; export * from './Structs/Ticket.js'; +// export * from './Structs/CustomMerkleMap.js'; export * from './util.js'; export * from './Structs/CustomMerkleMap.js'; export * from './constants.js'; export * from './StateManager/PStateManager.js'; -export * from './constants'; +export * from './StateManager/RandomManagerManager.js'; +export * from './StateManager/FactoryStateManager.js'; import { DistributionProofPublicInput,