diff --git a/config.json b/config.json index 2eebf8c..63d79d0 100644 --- a/config.json +++ b/config.json @@ -143,6 +143,15 @@ "feepayerAlias": "deployer", "fee": "0.1", "smartContract": "PLottery" + }, + "plottery_6": { + "networkId": "testnet", + "url": "https://api.minascan.io/node/devnet/v1/graphql", + "keyPath": "keys/plottery_6.json", + "feepayerKeyPath": "/home/alex/.cache/zkapp-cli/keys/deployer.json", + "feepayerAlias": "deployer", + "fee": "0.1", + "smartContract": "PLottery" } } } diff --git a/src/PLottery.test.ts b/src/PLottery.test.ts index cfd5ddf..0662137 100644 --- a/src/PLottery.test.ts +++ b/src/PLottery.test.ts @@ -221,10 +221,153 @@ describe('Add', () => { checkConsistancy(); }); + it('Refund check', async () => { + await localDeploy(); + + let curRound = 0; + + const balanceBefore = Mina.getBalance(senderAccount); + + // Buy ticket + const ticket = Ticket.from(mockWinningCombination, senderAccount, 1); + + let tx = await Mina.transaction(senderAccount, async () => { + await lottery.buyTicket(ticket, Field.from(curRound)); + }); + + await tx.prove(); + await tx.sign([senderKey]).send(); + + const balanceAfter = Mina.getBalance(senderAccount); + + expect(balanceBefore.sub(balanceAfter)).toEqual(TICKET_PRICE); + + checkConsistancy(); + + // Buy second ticket + let tx1_1 = await Mina.transaction(senderAccount, async () => { + await lottery.buyTicket(ticket, Field.from(curRound)); + }); + + await tx1_1.prove(); + await tx1_1.sign([senderKey]).send(); + + // Wait 3 more rounds + mineNBlocks(3 * BLOCK_PER_ROUND + 1); + + // Buy dummy ticket in next round, so reudcer 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(); + checkConsistancy(); + + // Get refund + + let { + roundWitness, + roundTicketWitness, + resultWitness: resultWitness1, + // bankValue, + // bankWitness, + nullifierWitness, + } = await state.getRefund(0, ticket); + + const balanceBefore2 = Mina.getBalance(senderAccount); + + let tx3 = await Mina.transaction(senderAccount, async () => { + await lottery.refund( + ticket, + roundWitness, + roundTicketWitness, + resultWitness1, + // bankValue, + // bankWitness, + nullifierWitness + ); + }); + + await tx3.prove(); + await tx3.sign([senderKey]).send(); + checkConsistancy(); + + const balanceAfter2 = Mina.getBalance(senderAccount); + + expect(balanceAfter2.sub(balanceBefore2)).toEqual( + TICKET_PRICE.mul(97).div(100) + ); + + // Produce result + let { resultWitness, bankValue, bankWitness } = + state.updateResult(curRound); + let tx4 = await Mina.transaction(senderAccount, async () => { + await lottery.produceResult( + resultWitness, + mockResult, + bankValue, + bankWitness + ); + }); + + await tx4.prove(); + await tx4.sign([senderKey]).send(); + checkConsistancy(); + + const balanceBefore3 = Mina.getBalance(senderAccount); + + // Get reward for second transaction + const rp = await state.getReward(curRound, ticket, undefined, 2); + let tx5 = await Mina.transaction(senderAccount, async () => { + await lottery.getReward( + ticket, + rp.roundWitness, + rp.roundTicketWitness, + rp.dp, + rp.winningNumbers, + rp.resultWitness, + rp.bankValue, + rp.bankWitness, + rp.nullifierWitness + ); + }); + + await tx5.prove(); + await tx5.sign([senderKey]).send(); + checkConsistancy(); + + const balanceAfter3 = Mina.getBalance(senderAccount); + + console.log(`Bank: ${state.bankMap.get(Field(0)).toString()}`); + console.log(); + + expect(balanceAfter3.sub(balanceBefore3)).toEqual( + TICKET_PRICE.mul(97).div(100) + ); + }); + it('Multiple round test', async () => { await localDeploy(); - const amountOfRounds = 10; + const amountOfRounds = 5; const amountOfTickets = 10; for (let round = 0; round < amountOfRounds; round++) { diff --git a/src/PLottery.ts b/src/PLottery.ts index 635705e..d70bc78 100644 --- a/src/PLottery.ts +++ b/src/PLottery.ts @@ -289,8 +289,6 @@ export class PLottery extends SmartContract { roundWitness: MerkleMap20Witness, roundTicketWitness: MerkleMap20Witness, resultWitness: MerkleMap20Witness, - bankValue: Field, - bankWitness: MerkleMap20Witness, nullifierWitness: MerkleMapWitness ) { // Check taht owner trying to claim @@ -312,8 +310,11 @@ export class PLottery extends SmartContract { // Check and update bank witness const totalTicketPrice = ticket.amount.mul(TICKET_PRICE); - const newBankValue = bankValue.sub(totalTicketPrice.value); - this.checkAndUpdateBank(bankWitness, round, bankValue, newBankValue); + const priceWithoutCommision = totalTicketPrice + .mul(PRESICION - COMMISION) + .div(PRESICION); + // const newBankValue = bankValue.sub(totalTicketPrice.value); + // this.checkAndUpdateBank(bankWitness, round, bankValue, newBankValue); // Check and update nullifier this.checkAndUpdateNullifier( @@ -326,7 +327,7 @@ export class PLottery extends SmartContract { // Send ticket price back to user this.send({ to: ticket.owner, - amount: totalTicketPrice, + amount: priceWithoutCommision, }); this.emitEvent( @@ -500,20 +501,20 @@ export class PLottery extends SmartContract { return this.checkMap(this.roundResultRoot, witness, round, curValue); } - private checkAndUpdateResult( - witness: MerkleMap20Witness, - round: Field, - curValue: Field, - newValue: Field - ): MerkleCheckResult { - return this.checkAndUpdateMap( - this.roundResultRoot, - witness, - round, - curValue, - newValue - ); - } + // private checkAndUpdateResult( + // witness: MerkleMap20Witness, + // round: Field, + // curValue: Field, + // newValue: Field + // ): MerkleCheckResult { + // return this.checkAndUpdateMap( + // this.roundResultRoot, + // witness, + // round, + // curValue, + // newValue + // ); + // } private checkBank( witness: MerkleMap20Witness, @@ -587,22 +588,22 @@ export class PLottery extends SmartContract { }; } - private checkAndUpdateTicketMap( - firstWitness: MerkleMap20Witness | MerkleMapWitness, - key1: Field | null, - secondWitness: MerkleMap20Witness | MerkleMapWitness, - // key2: Field, For know second level key is not checked as later it would transform to merkle map - prevValue: Field, - newValue: Field - ): { ticketId: Field; round: Field } { - const res = this.checkTicket(firstWitness, key1, secondWitness, prevValue); - - const [newRoot2] = secondWitness.computeRootAndKey(newValue); - const [newRoot1] = firstWitness.computeRootAndKey(newRoot2); - this.ticketRoot.set(newRoot1); - - return res; - } + // private checkAndUpdateTicketMap( + // firstWitness: MerkleMap20Witness | MerkleMapWitness, + // key1: Field | null, + // secondWitness: MerkleMap20Witness | MerkleMapWitness, + // // key2: Field, For know second level key is not checked as later it would transform to merkle map + // prevValue: Field, + // newValue: Field + // ): { ticketId: Field; round: Field } { + // const res = this.checkTicket(firstWitness, key1, secondWitness, prevValue); + + // const [newRoot2] = secondWitness.computeRootAndKey(newValue); + // const [newRoot1] = firstWitness.computeRootAndKey(newRoot2); + // this.ticketRoot.set(newRoot1); + + // return res; + // } private checkTicket( firstWitness: MerkleMap20Witness | MerkleMapWitness, diff --git a/src/StateManager/BaseStateManager.ts b/src/StateManager/BaseStateManager.ts index 76d661e..8f2d963 100644 --- a/src/StateManager/BaseStateManager.ts +++ b/src/StateManager/BaseStateManager.ts @@ -210,7 +210,8 @@ export class BaseStateManager { async getReward( round: number, ticket: Ticket, - roundDP: JsonProof | undefined = undefined + roundDP: JsonProof | undefined = undefined, + ticketIndex: number = 1 // If two or more same tickets presented ): Promise<{ roundWitness: MerkleMap20Witness; roundTicketWitness: MerkleMap20Witness; @@ -234,10 +235,13 @@ export class BaseStateManager { .equals(ticketHash) .toBoolean() ) { - roundTicketWitness = this.roundTicketMap[round].getWitness( - Field.from(ticketId) - ); - break; + ticketIndex--; + if (ticketIndex == 0) { + roundTicketWitness = this.roundTicketMap[round].getWitness( + Field.from(ticketId) + ); + break; + } } } if (!roundTicketWitness) { @@ -286,8 +290,8 @@ export class BaseStateManager { roundWitness: MerkleMap20Witness; roundTicketWitness: MerkleMap20Witness; resultWitness: MerkleMap20Witness; - bankValue: Field; - bankWitness: MerkleMap20Witness; + // bankValue: Field; + // bankWitness: MerkleMap20Witness; nullifierWitness: MerkleMapWitness; }> { const roundWitness = this.ticketMap.getWitness(Field.from(round)); @@ -315,8 +319,8 @@ export class BaseStateManager { 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 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)) @@ -328,18 +332,18 @@ export class BaseStateManager { Field(1) ); - this.bankMap.set( - Field.from(round), - bankValue.sub(ticket.amount.mul(TICKET_PRICE).value) - ); + // this.bankMap.set( + // Field.from(round), + // bankValue.sub(ticket.amount.mul(TICKET_PRICE).value) + // ); } return { roundWitness, roundTicketWitness, resultWitness, - bankValue, - bankWitness, + // bankValue, + // bankWitness, nullifierWitness, }; }