From 97b4d440917f77ae5e5b4d1827233bcc0e59ab81 Mon Sep 17 00:00:00 2001 From: phn210 Date: Wed, 24 Jan 2024 01:57:46 +0100 Subject: [PATCH] update storage and format --- .prettierrc | 2 +- package.json | 137 +- src/constants.ts | 44 +- src/contracts/Campaign.ts | 544 ++++---- src/contracts/CampaignStorage.ts | 266 ++-- src/contracts/Funding.ts | 923 +++++++------- src/contracts/FundingStorage.ts | 168 ++- src/contracts/Participation.ts | 616 ++++----- src/contracts/ParticipationStorage.ts | 330 +++-- src/contracts/Project.ts | 655 +++++----- src/contracts/ProjectStorage.ts | 386 +++--- src/contracts/SharedStorage.ts | 219 ++-- src/contracts/Treasury.ts | 526 ++++---- src/contracts/TreasuryStorage.ts | 177 +-- src/contracts/storages.ts | 10 +- src/libs/utils.ts | 238 ++-- src/scripts/Campaign.test.ts | 326 ++--- src/scripts/Funding.test.ts | 643 +++++----- src/scripts/Participation.test.ts | 80 +- src/scripts/Platform.ts | 1696 +++++++++++++------------ src/scripts/Project.test.ts | 234 ++-- src/scripts/Treasury.test.ts | 2 +- src/scripts/helper/config.ts | 30 +- src/scripts/helper/profiler.ts | 84 +- src/scripts/helper/randomAccounts.ts | 22 +- src/scripts/mock/campaigns.json | 76 +- src/scripts/mock/participations.json | 40 +- src/scripts/mock/projects.json | 62 +- src/scripts/updatePrivateKey.ts | 147 +-- tsconfig.noscripts.json | 4 + 30 files changed, 4599 insertions(+), 4088 deletions(-) create mode 100644 tsconfig.noscripts.json diff --git a/.prettierrc b/.prettierrc index 92f97e7..4865e76 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "semi": true, "singleQuote": true, - "tabWidth": 2, + "tabWidth": 4, "trailingComma": "es5" } diff --git a/package.json b/package.json index b543539..b1b3558 100644 --- a/package.json +++ b/package.json @@ -1,71 +1,72 @@ { - "name": "@auxo-dev/platform", - "version": "0.1.3", - "description": "Auxo's On-chain Funding platform on Mina blockchain", - "author": "", - "license": "Apache-2.0", - "keywords": [ - "mina-zkapp", - "mina-zk-app", - "mina-dapp", - "zkapp" - ], - "main": "./build/esm/src/index.js", - "types": "./build/esm/src/index.d.ts", - "scripts": { - "build": "tsc --build tsconfig.json tsconfig.cjs.json tsconfig.types.json && ./fix-export.sh", - "scripts": "tsc --build tsconfig.json tsconfig.cjs.json tsconfig.types.json && ./fix-export.sh && node", - "buildw": "tsc --watch", - "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage", - "format": "prettier --write --ignore-unknown **/*", - "prepare": "husky install", - "test": "tsc && node --experimental-vm-modules node_modules/jest/bin/jest.js", - "testw": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", - "lint": "npx eslint src/* --fix" - }, - "exports": { - ".": { - "types": "./build/types/src/index.d.ts", - "require": "./build/cjs/src/index.js", - "import": "./build/esm/src/index.js", - "default": "./build/esm/src/index.js" + "name": "@auxo-dev/platform", + "version": "0.1.3", + "description": "Auxo's On-chain Funding platform on Mina blockchain", + "author": "", + "license": "Apache-2.0", + "keywords": [ + "mina-zkapp", + "mina-zk-app", + "mina-dapp", + "zkapp" + ], + "main": "./build/esm/src/index.js", + "types": "./build/esm/src/index.d.ts", + "scripts": { + "build": "tsc --build tsconfig.json tsconfig.cjs.json tsconfig.types.json && ./fix-export.sh", + "build:no-scripts": "tsc --build tsconfig.noscripts.json", + "scripts": "tsc --build tsconfig.json tsconfig.cjs.json tsconfig.types.json && ./fix-export.sh && node", + "buildw": "tsc --watch", + "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage", + "format": "prettier --write --ignore-unknown **/*", + "prepare": "husky install", + "test": "tsc && node --experimental-vm-modules node_modules/jest/bin/jest.js", + "testw": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", + "lint": "npx eslint src/* --fix" }, - "./*": { - "types": "./*.d.ts", - "require": "./*.js", - "import": "./*.js", - "default": "./*.js" + "exports": { + ".": { + "types": "./build/types/src/index.d.ts", + "require": "./build/cjs/src/index.js", + "import": "./build/esm/src/index.js", + "default": "./build/esm/src/index.js" + }, + "./*": { + "types": "./*.d.ts", + "require": "./*.js", + "import": "./*.js", + "default": "./*.js" + } + }, + "files": [ + "build/*", + "build/**/*.map" + ], + "lint-staged": { + "**/*": [ + "eslint src/* --fix --ignore-pattern *.sh", + "prettier --write --ignore-unknown" + ] + }, + "devDependencies": { + "@babel/preset-env": "^7.16.4", + "@babel/preset-typescript": "^7.16.0", + "@types/jest": "^27.0.3", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "eslint": "^8.7.0", + "eslint-plugin-o1js": "^0.4.0", + "husky": "^7.0.1", + "jest": "^27.3.1", + "lint-staged": "^11.0.1", + "prettier": "^2.3.2", + "ts-jest": "^27.0.7", + "typescript": "^4.7.2", + "zkapp-cli": "^0.15.0" + }, + "dependencies": { + "@auxo-dev/dkg": "0.2.9", + "@auxo-dev/auxo-libs": "0.3.5", + "o1js": "0.15.1" } - }, - "files": [ - "build/*", - "build/**/*.map" - ], - "lint-staged": { - "**/*": [ - "eslint src/* --fix --ignore-pattern *.sh", - "prettier --write --ignore-unknown" - ] - }, - "devDependencies": { - "@babel/preset-env": "^7.16.4", - "@babel/preset-typescript": "^7.16.0", - "@types/jest": "^27.0.3", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "eslint": "^8.7.0", - "eslint-plugin-o1js": "^0.4.0", - "husky": "^7.0.1", - "jest": "^27.3.1", - "lint-staged": "^11.0.1", - "prettier": "^2.3.2", - "ts-jest": "^27.0.7", - "typescript": "^4.7.2", - "zkapp-cli": "^0.15.0" - }, - "dependencies": { - "@auxo-dev/dkg": "0.2.9", - "@auxo-dev/auxo-libs": "0.3.5", - "o1js": "0.15.1" - } -} \ No newline at end of file +} diff --git a/src/constants.ts b/src/constants.ts index 2835bee..6830735 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,34 +2,34 @@ import { Constants } from '@auxo-dev/dkg'; export const PROJECT_MEMBER_MAX_SIZE = 2 ** 3; export const ADDRESS_MAX_SIZE = 16; export const INSTANCE_LIMITS = { - PROJECT: 2 ** 5, - CAMPAIGN: 2 ** 5, - PARTICIPATION: Constants.REQUEST_MAX_SIZE, + PROJECT: 2 ** 5, + CAMPAIGN: 2 ** 5, + PARTICIPATION: Constants.REQUEST_MAX_SIZE, }; export enum Contract { - COMMITTEE = 'committee', - DKG = 'dkg', - ROUND1 = 'round1', - ROUND2 = 'round2', - RESPONSE = 'response', - REQUEST = 'request', - PROJECT = 'project', - CAMPAIGN = 'campaign', - PARTICIPATION = 'participation', - FUNDING = 'funding', - TREASURY = 'treasury', + COMMITTEE = 'committee', + DKG = 'dkg', + ROUND1 = 'round1', + ROUND2 = 'round2', + RESPONSE = 'response', + REQUEST = 'request', + PROJECT = 'project', + CAMPAIGN = 'campaign', + PARTICIPATION = 'participation', + FUNDING = 'funding', + TREASURY = 'treasury', } function createEnumIndexMap(enumObj: any): { [key: string]: number } { - const map: { [key: string]: number } = {}; - let index = 0; - for (const key in enumObj) { - if (enumObj.hasOwnProperty(key)) { - map[enumObj[key].toUpperCase()] = index++; + const map: { [key: string]: number } = {}; + let index = 0; + for (const key in enumObj) { + if (enumObj.hasOwnProperty(key)) { + map[enumObj[key].toUpperCase()] = index++; + } } - } - // console.log(map); - return map; + // console.log(map); + return map; } export const ZkAppEnum = createEnumIndexMap(Contract); diff --git a/src/contracts/Campaign.ts b/src/contracts/Campaign.ts index a82f40d..c17690f 100644 --- a/src/contracts/Campaign.ts +++ b/src/contracts/Campaign.ts @@ -1,305 +1,319 @@ import { - Field, - SmartContract, - state, - State, - method, - Reducer, - Struct, - SelfProof, - Poseidon, - Provable, - ZkProgram, - PublicKey, - Void, - Bool, + Field, + SmartContract, + state, + State, + method, + Reducer, + Struct, + SelfProof, + Poseidon, + Provable, + ZkProgram, + PublicKey, + Void, + Bool, } from 'o1js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; import { updateOutOfSnark } from '../libs/utils.js'; import { - EMPTY_LEVEL_1_TREE, - Level1Witness, - StatusEnum, - ConfigStorage, - OwnerStorage, - InfoStorage, + EMPTY_LEVEL_1_TREE, + Level1Witness, + StatusEnum, + ConfigStorage, + OwnerStorage, + InfoStorage, } from './CampaignStorage.js'; const DefaultLevel1Root = EMPTY_LEVEL_1_TREE().getRoot(); export class CampaignAction extends Struct({ - campaignId: Field, - ipfsHash: IPFSHash, - owner: PublicKey, - campaignStatus: Field, - committeeId: Field, - keyId: Field, + campaignId: Field, + ipfsHash: IPFSHash, + owner: PublicKey, + campaignStatus: Field, + committeeId: Field, + keyId: Field, }) { - static fromFields(fields: Field[]): CampaignAction { - return super.fromFields(fields) as CampaignAction; - } + static fromFields(fields: Field[]): CampaignAction { + return super.fromFields(fields) as CampaignAction; + } } export class CheckCampaignOwerInput extends Struct({ - owner: PublicKey, - campaignId: Field, - ownerWitness: Level1Witness, + owner: PublicKey, + campaignId: Field, + ownerWitness: Level1Witness, }) {} export class CreateCampaignInput extends Struct({ - ipfsHash: IPFSHash, - committeeId: Field, - keyId: Field, + ipfsHash: IPFSHash, + committeeId: Field, + keyId: Field, }) { - static fromFields(fields: Field[]): CreateCampaignInput { - return super.fromFields(fields) as CreateCampaignInput; - } + static fromFields(fields: Field[]): CreateCampaignInput { + return super.fromFields(fields) as CreateCampaignInput; + } } export class UpdateCampaignInput extends Struct({}) { - static fromFields(fields: Field[]): UpdateCampaignInput { - return super.fromFields(fields) as UpdateCampaignInput; - } + static fromFields(fields: Field[]): UpdateCampaignInput { + return super.fromFields(fields) as UpdateCampaignInput; + } } export class CreateCampaignProofOutput extends Struct({ - initialOwnerTreeRoot: Field, - initialInfoTreeRoot: Field, - initialStatusTreeRoot: Field, - initialConfigTreeRoot: Field, - initialNextCampaignId: Field, - initialLastRolledUpACtionState: Field, - finalOwnerTreeRoot: Field, - finalInfoTreeRoot: Field, - finalStatusTreeRoot: Field, - finalConfigTreeRoot: Field, - finalNextCampaignId: Field, - finalLastRolledUpActionState: Field, + initialOwnerTreeRoot: Field, + initialInfoTreeRoot: Field, + initialStatusTreeRoot: Field, + initialConfigTreeRoot: Field, + initialNextCampaignId: Field, + initialLastRolledUpACtionState: Field, + finalOwnerTreeRoot: Field, + finalInfoTreeRoot: Field, + finalStatusTreeRoot: Field, + finalConfigTreeRoot: Field, + finalNextCampaignId: Field, + finalLastRolledUpActionState: Field, }) { - hash(): Field { - return Poseidon.hash(CreateCampaignProofOutput.toFields(this)); - } + hash(): Field { + return Poseidon.hash(CreateCampaignProofOutput.toFields(this)); + } } export const CreateCampaign = ZkProgram({ - name: 'create-campaign', - publicOutput: CreateCampaignProofOutput, - methods: { - firstStep: { - privateInputs: [Field, Field, Field, Field, Field, Field], - method( - initialOwnerTreeRoot, - initialInfoTreeRoot, - initialStatusTreeRoot, - initialConfigTreeRoot, - initialNextCampaignId, - initialLastRolledUpACtionState - ): CreateCampaignProofOutput { - return new CreateCampaignProofOutput({ - initialOwnerTreeRoot, - initialInfoTreeRoot, - initialStatusTreeRoot, - initialConfigTreeRoot, - initialNextCampaignId, - initialLastRolledUpACtionState, - finalOwnerTreeRoot: initialOwnerTreeRoot, - finalInfoTreeRoot: initialInfoTreeRoot, - finalStatusTreeRoot: initialStatusTreeRoot, - finalConfigTreeRoot: initialConfigTreeRoot, - finalNextCampaignId: initialNextCampaignId, - finalLastRolledUpActionState: initialLastRolledUpACtionState, - }); - }, + name: 'create-campaign', + publicOutput: CreateCampaignProofOutput, + methods: { + firstStep: { + privateInputs: [Field, Field, Field, Field, Field, Field], + method( + initialOwnerTreeRoot, + initialInfoTreeRoot, + initialStatusTreeRoot, + initialConfigTreeRoot, + initialNextCampaignId, + initialLastRolledUpACtionState + ): CreateCampaignProofOutput { + return new CreateCampaignProofOutput({ + initialOwnerTreeRoot, + initialInfoTreeRoot, + initialStatusTreeRoot, + initialConfigTreeRoot, + initialNextCampaignId, + initialLastRolledUpACtionState, + finalOwnerTreeRoot: initialOwnerTreeRoot, + finalInfoTreeRoot: initialInfoTreeRoot, + finalStatusTreeRoot: initialStatusTreeRoot, + finalConfigTreeRoot: initialConfigTreeRoot, + finalNextCampaignId: initialNextCampaignId, + finalLastRolledUpActionState: + initialLastRolledUpACtionState, + }); + }, + }, + createCampaign: { + privateInputs: [ + SelfProof, + CampaignAction, + Level1Witness, + Level1Witness, + Level1Witness, + Level1Witness, + ], + method( + preProof: SelfProof, + newAction: CampaignAction, + ownerWitness: Level1Witness, + infoWitess: Level1Witness, + statusWitess: Level1Witness, + configWitess: Level1Witness + ): CreateCampaignProofOutput { + preProof.verify(); + + // check if this action is create campaign + newAction.campaignId.assertEquals(Field(-1)); + + let newCampaignId = preProof.publicOutput.finalNextCampaignId; + + ////// caculate new ownerTreeRoot + let preOwnerRoot = ownerWitness.calculateRoot(Field(0)); + let ownerIndex = ownerWitness.calculateIndex(); + ownerIndex.assertEquals(newCampaignId); + preOwnerRoot.assertEquals( + preProof.publicOutput.finalOwnerTreeRoot + ); + + // update ownerTreeRoot + let newOwnerTreeRoot = ownerWitness.calculateRoot( + OwnerStorage.calculateLeaf(newAction.owner) + ); + + ////// caculate in infoTreeRoot + let preInfoRoot = infoWitess.calculateRoot(Field(0)); + let infoIndex = infoWitess.calculateIndex(); + infoIndex.assertEquals(newCampaignId); + preInfoRoot.assertEquals( + preProof.publicOutput.finalInfoTreeRoot + ); + + // update infoTreeRoot + let newInfoTreeRoot = infoWitess.calculateRoot( + InfoStorage.calculateLeaf(newAction.ipfsHash) + ); + + ////// caculate in infoTreeRoot + let preStatusRoot = statusWitess.calculateRoot(Field(0)); + let statusIndex = statusWitess.calculateIndex(); + statusIndex.assertEquals(newCampaignId); + preStatusRoot.assertEquals( + preProof.publicOutput.finalStatusTreeRoot + ); + + // update infoTreeRoot + let newStatusTreeRoot = statusWitess.calculateRoot( + newAction.campaignStatus + ); + + ////// caculate in configTreeRoot + let preConfigRoot = configWitess.calculateRoot(Field(0)); + let configIndex = configWitess.calculateIndex(); + configIndex.assertEquals(newCampaignId); + preConfigRoot.assertEquals( + preProof.publicOutput.finalConfigTreeRoot + ); + + // update infoTreeRoot + let newConfigTreeRoot = configWitess.calculateRoot( + ConfigStorage.calculateLeaf({ + committeeId: newAction.committeeId, + keyId: newAction.keyId, + }) + ); + + return new CreateCampaignProofOutput({ + initialOwnerTreeRoot: + preProof.publicOutput.initialOwnerTreeRoot, + initialInfoTreeRoot: + preProof.publicOutput.initialInfoTreeRoot, + initialStatusTreeRoot: + preProof.publicOutput.initialStatusTreeRoot, + initialConfigTreeRoot: + preProof.publicOutput.initialConfigTreeRoot, + initialNextCampaignId: + preProof.publicOutput.initialNextCampaignId, + initialLastRolledUpACtionState: + preProof.publicOutput.initialLastRolledUpACtionState, + finalOwnerTreeRoot: newOwnerTreeRoot, + finalInfoTreeRoot: newInfoTreeRoot, + finalStatusTreeRoot: newStatusTreeRoot, + finalConfigTreeRoot: newConfigTreeRoot, + finalNextCampaignId: + preProof.publicOutput.finalNextCampaignId.add(Field(1)), + finalLastRolledUpActionState: updateOutOfSnark( + preProof.publicOutput.finalLastRolledUpActionState, + [CampaignAction.toFields(newAction)] + ), + }); + }, + }, }, - createCampaign: { - privateInputs: [ - SelfProof, - CampaignAction, - Level1Witness, - Level1Witness, - Level1Witness, - Level1Witness, - ], - method( - preProof: SelfProof, - newAction: CampaignAction, - ownerWitness: Level1Witness, - infoWitess: Level1Witness, - statusWitess: Level1Witness, - configWitess: Level1Witness - ): CreateCampaignProofOutput { - preProof.verify(); - - // check if this action is create campaign - newAction.campaignId.assertEquals(Field(-1)); - - let newCampaignId = preProof.publicOutput.finalNextCampaignId; - - ////// caculate new ownerTreeRoot - let preOwnerRoot = ownerWitness.calculateRoot(Field(0)); - let ownerIndex = ownerWitness.calculateIndex(); - ownerIndex.assertEquals(newCampaignId); - preOwnerRoot.assertEquals(preProof.publicOutput.finalOwnerTreeRoot); - - // update ownerTreeRoot - let newOwnerTreeRoot = ownerWitness.calculateRoot( - OwnerStorage.calculateLeaf(newAction.owner) - ); - - ////// caculate in infoTreeRoot - let preInfoRoot = infoWitess.calculateRoot(Field(0)); - let infoIndex = infoWitess.calculateIndex(); - infoIndex.assertEquals(newCampaignId); - preInfoRoot.assertEquals(preProof.publicOutput.finalInfoTreeRoot); +}); - // update infoTreeRoot - let newInfoTreeRoot = infoWitess.calculateRoot( - InfoStorage.calculateLeaf(newAction.ipfsHash) - ); +class CampaignProof extends ZkProgram.Proof(CreateCampaign) {} - ////// caculate in infoTreeRoot - let preStatusRoot = statusWitess.calculateRoot(Field(0)); - let statusIndex = statusWitess.calculateIndex(); - statusIndex.assertEquals(newCampaignId); - preStatusRoot.assertEquals(preProof.publicOutput.finalStatusTreeRoot); +export enum EventEnum { + CAMPAIGN_CREATED = 'campaign-created', +} - // update infoTreeRoot - let newStatusTreeRoot = statusWitess.calculateRoot( - newAction.campaignStatus +export class CampaignContract extends SmartContract { + // store owner of campaign + @state(Field) ownerTreeRoot = State(); + // store IPFS hash of campaign + @state(Field) infoTreeRoot = State(); + // status of the campaign, check enum Status + @state(Field) statusTreeRoot = State(); + // hash(committeeId, keyId) + @state(Field) configTreeRoot = State(); + // MT of other zkApp address + @state(Field) zkApps = State(); + // next campaign Id + @state(Field) nextCampaignId = State(); + @state(Field) lastRolledUpActionState = State(); + + reducer = Reducer({ actionType: CampaignAction }); + + events = { + [EventEnum.CAMPAIGN_CREATED]: Field, + }; + + init() { + super.init(); + this.ownerTreeRoot.set(DefaultLevel1Root); + this.infoTreeRoot.set(DefaultLevel1Root); + this.statusTreeRoot.set(DefaultLevel1Root); + this.configTreeRoot.set(DefaultLevel1Root); + this.lastRolledUpActionState.set(Reducer.initialActionState); + } + + @method createCampaign(input: CreateCampaignInput) { + this.reducer.dispatch( + new CampaignAction({ + campaignId: Field(-1), + ipfsHash: input.ipfsHash, + owner: this.sender, + campaignStatus: Field(StatusEnum.APPLICATION), + committeeId: input.committeeId, + keyId: input.keyId, + }) + ); + } + + // TODO + // eslint-disable-next-line @typescript-eslint/no-empty-function + @method updateCampaignInfo(input: UpdateCampaignInput) {} + + @method rollup(proof: CampaignProof) { + proof.verify(); + let ownerTreeRoot = this.ownerTreeRoot.getAndRequireEquals(); + let infoTreeRoot = this.infoTreeRoot.getAndRequireEquals(); + let statusTreeRoot = this.statusTreeRoot.getAndRequireEquals(); + let configTreeRoot = this.configTreeRoot.getAndRequireEquals(); + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + ownerTreeRoot.assertEquals(proof.publicOutput.initialOwnerTreeRoot); + infoTreeRoot.assertEquals(proof.publicOutput.initialInfoTreeRoot); + statusTreeRoot.assertEquals(proof.publicOutput.initialStatusTreeRoot); + configTreeRoot.assertEquals(proof.publicOutput.initialConfigTreeRoot); + lastRolledUpActionState.assertEquals( + proof.publicOutput.initialLastRolledUpACtionState ); - ////// caculate in configTreeRoot - let preConfigRoot = configWitess.calculateRoot(Field(0)); - let configIndex = configWitess.calculateIndex(); - configIndex.assertEquals(newCampaignId); - preConfigRoot.assertEquals(preProof.publicOutput.finalConfigTreeRoot); - - // update infoTreeRoot - let newConfigTreeRoot = configWitess.calculateRoot( - ConfigStorage.calculateLeaf({ - committeeId: newAction.committeeId, - keyId: newAction.keyId, - }) + let lastActionState = this.account.actionState.getAndRequireEquals(); + lastActionState.assertEquals( + proof.publicOutput.finalLastRolledUpActionState ); - return new CreateCampaignProofOutput({ - initialOwnerTreeRoot: preProof.publicOutput.initialOwnerTreeRoot, - initialInfoTreeRoot: preProof.publicOutput.initialInfoTreeRoot, - initialStatusTreeRoot: preProof.publicOutput.initialStatusTreeRoot, - initialConfigTreeRoot: preProof.publicOutput.initialConfigTreeRoot, - initialNextCampaignId: preProof.publicOutput.initialNextCampaignId, - initialLastRolledUpACtionState: - preProof.publicOutput.initialLastRolledUpACtionState, - finalOwnerTreeRoot: newOwnerTreeRoot, - finalInfoTreeRoot: newInfoTreeRoot, - finalStatusTreeRoot: newStatusTreeRoot, - finalConfigTreeRoot: newConfigTreeRoot, - finalNextCampaignId: preProof.publicOutput.finalNextCampaignId.add( - Field(1) - ), - finalLastRolledUpActionState: updateOutOfSnark( - preProof.publicOutput.finalLastRolledUpActionState, - [CampaignAction.toFields(newAction)] - ), - }); - }, - }, - }, -}); + // update on-chain state + this.ownerTreeRoot.set(proof.publicOutput.finalOwnerTreeRoot); + this.infoTreeRoot.set(proof.publicOutput.finalInfoTreeRoot); + this.statusTreeRoot.set(proof.publicOutput.finalStatusTreeRoot); + this.configTreeRoot.set(proof.publicOutput.finalConfigTreeRoot); + this.lastRolledUpActionState.set( + proof.publicOutput.finalLastRolledUpActionState + ); -class CampaignProof extends ZkProgram.Proof(CreateCampaign) {} + this.emitEvent( + EventEnum.CAMPAIGN_CREATED, + proof.publicOutput.finalNextCampaignId.sub(Field(1)) + ); + } -export enum EventEnum { - CAMPAIGN_CREATED = 'campaign-created', -} + // TODO + @method checkCampaignOwner(input: CheckCampaignOwerInput): Bool { + let isOwner = Bool(true); -export class CampaignContract extends SmartContract { - // store owner of campaign - @state(Field) ownerTreeRoot = State(); - // store IPFS hash of campaign - @state(Field) infoTreeRoot = State(); - // status of the campaign, check enum Status - @state(Field) statusTreeRoot = State(); - // hash(committeeId, keyId) - @state(Field) configTreeRoot = State(); - // MT of other zkApp address - @state(Field) zkApps = State(); - // next campaign Id - @state(Field) nextCampaignId = State(); - @state(Field) lastRolledUpActionState = State(); - - reducer = Reducer({ actionType: CampaignAction }); - - events = { - [EventEnum.CAMPAIGN_CREATED]: Field, - }; - - init() { - super.init(); - this.ownerTreeRoot.set(DefaultLevel1Root); - this.infoTreeRoot.set(DefaultLevel1Root); - this.statusTreeRoot.set(DefaultLevel1Root); - this.configTreeRoot.set(DefaultLevel1Root); - this.lastRolledUpActionState.set(Reducer.initialActionState); - } - - @method createCampaign(input: CreateCampaignInput) { - this.reducer.dispatch( - new CampaignAction({ - campaignId: Field(-1), - ipfsHash: input.ipfsHash, - owner: this.sender, - campaignStatus: Field(StatusEnum.APPLICATION), - committeeId: input.committeeId, - keyId: input.keyId, - }) - ); - } - - // TODO - @method updateCampaignInfo(input: UpdateCampaignInput) {} - - @method rollup(proof: CampaignProof) { - proof.verify(); - let ownerTreeRoot = this.ownerTreeRoot.getAndRequireEquals(); - let infoTreeRoot = this.infoTreeRoot.getAndRequireEquals(); - let statusTreeRoot = this.statusTreeRoot.getAndRequireEquals(); - let configTreeRoot = this.configTreeRoot.getAndRequireEquals(); - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - ownerTreeRoot.assertEquals(proof.publicOutput.initialOwnerTreeRoot); - infoTreeRoot.assertEquals(proof.publicOutput.initialInfoTreeRoot); - statusTreeRoot.assertEquals(proof.publicOutput.initialStatusTreeRoot); - configTreeRoot.assertEquals(proof.publicOutput.initialConfigTreeRoot); - lastRolledUpActionState.assertEquals( - proof.publicOutput.initialLastRolledUpACtionState - ); - - let lastActionState = this.account.actionState.getAndRequireEquals(); - lastActionState.assertEquals( - proof.publicOutput.finalLastRolledUpActionState - ); - - // update on-chain state - this.ownerTreeRoot.set(proof.publicOutput.finalOwnerTreeRoot); - this.infoTreeRoot.set(proof.publicOutput.finalInfoTreeRoot); - this.statusTreeRoot.set(proof.publicOutput.finalStatusTreeRoot); - this.configTreeRoot.set(proof.publicOutput.finalConfigTreeRoot); - this.lastRolledUpActionState.set( - proof.publicOutput.finalLastRolledUpActionState - ); - - this.emitEvent( - EventEnum.CAMPAIGN_CREATED, - proof.publicOutput.finalNextCampaignId.sub(Field(1)) - ); - } - - // TODO - @method checkCampaignOwner(input: CheckCampaignOwerInput): Bool { - let isOwner = Bool(true); - - return isOwner; - } + return isOwner; + } } diff --git a/src/contracts/CampaignStorage.ts b/src/contracts/CampaignStorage.ts index c823ac8..ba66a3c 100644 --- a/src/contracts/CampaignStorage.ts +++ b/src/contracts/CampaignStorage.ts @@ -1,10 +1,9 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Field, MerkleTree, MerkleWitness, Poseidon, PublicKey } from 'o1js'; import { INSTANCE_LIMITS } from '../constants.js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; export const LEVEL_1_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; export class Level1MT extends MerkleTree {} export class Level1Witness extends MerkleWitness(LEVEL_1_TREE_HEIGHT) {} @@ -12,163 +11,166 @@ export class Level1Witness extends MerkleWitness(LEVEL_1_TREE_HEIGHT) {} export const EMPTY_LEVEL_1_TREE = () => new Level1MT(LEVEL_1_TREE_HEIGHT); // Storage -export abstract class CampaignStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - this.level1 = level1 || EMPTY_LEVEL_1_TREE(); - } - - abstract calculateLeaf(args: any): Field; - abstract calculateLevel1Index(args: any): Field; - calculateLevel2Index?(args: any): Field; - - getLevel1Witness(level1Index: Field): Level1Witness { - return new Level1Witness(this.level1.getWitness(level1Index.toBigInt())); - } - - getWitness(level1Index: Field): Level1Witness { - return this.getLevel1Witness(level1Index); - } - - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class CampaignStorage { + private _level1: Level1MT; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_TREE(); + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + leafs[i].level1Index, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + leafs[i].level1Index, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1Witness { + return new Level1Witness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getWitness(level1Index: Field): Level1Witness { + return this.getLevel1Witness(level1Index); + } + + updateLeaf(level1Index: Field, leaf: Field): void { + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf(level1Index: Field, rawLeaf: RawLeaf): void { + let leaf = this.calculateLeaf(rawLeaf); + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class InfoStorage extends CampaignStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } +export type InfoLeaf = IPFSHash; - static calculateLeaf(ipfsHash: IPFSHash): Field { - return Poseidon.hash(ipfsHash.toFields()); - } +export class InfoStorage extends CampaignStorage { + static calculateLeaf(ipfsHash: InfoLeaf): Field { + return Poseidon.hash(ipfsHash.toFields()); + } - calculateLeaf(ipfsHash: IPFSHash): Field { - return InfoStorage.calculateLeaf(ipfsHash); - } + calculateLeaf(ipfsHash: InfoLeaf): Field { + return InfoStorage.calculateLeaf(ipfsHash); + } - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } - - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(campaignId: Field): Field { + return InfoStorage.calculateLevel1Index(campaignId); + } } -export class OwnerStorage extends CampaignStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } +export type OwnerLeaf = PublicKey; - calculateLeaf(publicKey: PublicKey): Field { - return OwnerStorage.calculateLeaf(publicKey); - } +export class OwnerStorage extends CampaignStorage { + static calculateLeaf(publicKey: OwnerLeaf): Field { + return Poseidon.hash(publicKey.toFields()); + } - static calculateLeaf(publicKey: PublicKey): Field { - return Poseidon.hash(publicKey.toFields()); - } + calculateLeaf(publicKey: OwnerLeaf): Field { + return OwnerStorage.calculateLeaf(publicKey); + } - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } - - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(campaignId: Field): Field { + return OwnerStorage.calculateLevel1Index(campaignId); + } } -export class StatusStorage extends CampaignStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } +export type StatusLeaf = StatusEnum; - calculateLeaf(status: StatusEnum): Field { - return StatusStorage.calculateLeaf(status); - } +export class StatusStorage extends CampaignStorage { + static calculateLeaf(status: StatusLeaf): Field { + return Field(status); + } - static calculateLeaf(status: StatusEnum): Field { - return Field(status); - } + calculateLeaf(status: StatusLeaf): Field { + return StatusStorage.calculateLeaf(status); + } - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } - - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(campaignId: Field): Field { + return StatusStorage.calculateLevel1Index(campaignId); + } } -export class ConfigStorage extends CampaignStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } - - calculateLeaf({ - committeeId, - keyId, - }: { - committeeId: Field; - keyId: Field; - }): Field { - return ConfigStorage.calculateLeaf({ - committeeId, - keyId, - }); - } - - static calculateLeaf({ - committeeId, - keyId, - }: { +export type ConfigLeaf = { committeeId: Field; keyId: Field; - }): Field { - return Poseidon.hash([committeeId, keyId]); - } +}; + +export class ConfigStorage extends CampaignStorage { + static calculateLeaf(rawLeaf: ConfigLeaf): Field { + return Poseidon.hash([rawLeaf.committeeId, rawLeaf.keyId]); + } - calculateLevel1Index(projectId: Field): Field { - return projectId; - } + calculateLeaf(rawLeaf: ConfigLeaf): Field { + return ConfigStorage.calculateLeaf(rawLeaf); + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } + static calculateLevel1Index(projectId: Field): Field { + return projectId; + } - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(projectId: Field): Field { + return ConfigStorage.calculateLevel1Index(projectId); + } } // Type export const enum StatusEnum { - NOT_STARTED, - APPLICATION, - FUNDING, - ALLOCATED, - FINALIZE_ROUND_1, - __LENGTH, + NOT_STARTED, + APPLICATION, + FUNDING, + ALLOCATED, + FINALIZE_ROUND_1, + __LENGTH, } diff --git a/src/contracts/Funding.ts b/src/contracts/Funding.ts index 2bdc465..7150602 100644 --- a/src/contracts/Funding.ts +++ b/src/contracts/Funding.ts @@ -1,31 +1,31 @@ import { - Field, - SmartContract, - state, - State, - method, - PublicKey, - Group, - Reducer, - MerkleMapWitness, - Struct, - SelfProof, - Poseidon, - Provable, - Void, - Scalar, - ZkProgram, - Bool, - UInt64, - AccountUpdate, + Field, + SmartContract, + state, + State, + method, + PublicKey, + Group, + Reducer, + MerkleMapWitness, + Struct, + SelfProof, + Poseidon, + Provable, + Void, + Scalar, + ZkProgram, + Bool, + UInt64, + AccountUpdate, } from 'o1js'; import { CustomScalar, ScalarDynamicArray } from '@auxo-dev/auxo-libs'; import { updateOutOfSnark } from '../libs/utils.js'; import { - ZkApp as DKG_Contracts, - Constants as DKG_Constants, + ZkApp as DKG_Contracts, + Constants as DKG_Constants, } from '@auxo-dev/dkg'; import { ZkAppEnum } from '../constants.js'; @@ -33,491 +33,514 @@ import { ZkAppEnum } from '../constants.js'; import { ActionStatus, ZkAppRef, EMPTY_REDUCE_MT } from './SharedStorage.js'; import { - Level1Witness, - EMPTY_LEVEL_1_TREE, - ValueStorage, - RequestIdStorage, + Level1Witness, + EMPTY_LEVEL_1_TREE, + ValueStorage, + RequestIdStorage, } from './FundingStorage.js'; const DefaultLevel1Root = EMPTY_LEVEL_1_TREE().getRoot(); export enum EventEnum { - ACTIONS_REDUCED = 'actions-reduced', - REQUEST_SENT = 'request-sent', + ACTIONS_REDUCED = 'actions-reduced', + REQUEST_SENT = 'request-sent', } export class RequestSent extends Struct({ - campaignId: Field, - committeeId: Field, - keyId: Field, - requestId: Field, - sumR: DKG_Contracts.Request.RequestVector, - sumM: DKG_Contracts.Request.RequestVector, + campaignId: Field, + committeeId: Field, + keyId: Field, + requestId: Field, + sumR: DKG_Contracts.Request.RequestVector, + sumM: DKG_Contracts.Request.RequestVector, }) { - static fromFields(action: Field[]): RequestSent { - return super.fromFields(action) as RequestSent; - } + static fromFields(action: Field[]): RequestSent { + return super.fromFields(action) as RequestSent; + } } export class CustomScalarArray extends ScalarDynamicArray( - DKG_Constants.REQUEST_MAX_SIZE + DKG_Constants.REQUEST_MAX_SIZE ) {} export class FundingInput extends Struct({ - campaignId: Field, - committeePublicKey: PublicKey, - // TODO wintess to check if it the right publickey - secretVector: CustomScalarArray, - random: CustomScalarArray, - // settingMerkleMapWitness: MerkleMapWitness, - treasuryContract: ZkAppRef, + campaignId: Field, + committeePublicKey: PublicKey, + // TODO wintess to check if it the right publickey + secretVector: CustomScalarArray, + random: CustomScalarArray, + // settingMerkleMapWitness: MerkleMapWitness, + treasuryContract: ZkAppRef, }) {} export class FundingAction extends Struct({ - campaignId: Field, - R: DKG_Contracts.Request.RequestVector, - M: DKG_Contracts.Request.RequestVector, + campaignId: Field, + R: DKG_Contracts.Request.RequestVector, + M: DKG_Contracts.Request.RequestVector, }) { - hash(): Field { - return Poseidon.hash(FundingAction.toFields(this)); - } + hash(): Field { + return Poseidon.hash(FundingAction.toFields(this)); + } - static fromFields(action: Field[]): FundingAction { - return super.fromFields(action) as FundingAction; - } + static fromFields(action: Field[]): FundingAction { + return super.fromFields(action) as FundingAction; + } } export class CheckValueInput extends Struct({ - campaignId: Field, - Value: DKG_Contracts.Request.RequestVector, + campaignId: Field, + Value: DKG_Contracts.Request.RequestVector, }) {} export class ReduceOutput extends Struct({ - // Actually don't need initialActionState, since we check initialActionStatus and finalActionState on-chain - // Do this to increase security: from finding x,y that hash(x,y) = Z to finding x that hash(x,Y) = Z - initialActionState: Field, - initialActionStatus: Field, - finalActionState: Field, - finalActionStatus: Field, + // Actually don't need initialActionState, since we check initialActionStatus and finalActionState on-chain + // Do this to increase security: from finding x,y that hash(x,y) = Z to finding x that hash(x,Y) = Z + initialActionState: Field, + initialActionStatus: Field, + finalActionState: Field, + finalActionStatus: Field, }) {} export const CreateReduceProof = ZkProgram({ - name: 'create-rollup-status', - publicOutput: ReduceOutput, - methods: { - // First action to rollup - firstStep: { - privateInputs: [Field, Field], - method( - initialActionState: Field, - initialActionStatus: Field - ): ReduceOutput { - return new ReduceOutput({ - initialActionState, - initialActionStatus, - finalActionState: initialActionState, - finalActionStatus: initialActionStatus, - }); - }, - }, - // Next actions to rollup - nextStep: { - privateInputs: [ - SelfProof, - FundingAction, - MerkleMapWitness, - ], - method( - earlierProof: SelfProof, - action: FundingAction, - rollupStatusWitness: MerkleMapWitness - ): ReduceOutput { - // Verify earlier proof - earlierProof.verify(); - - // Calculate new action state == action id in the tree - let newActionState = updateOutOfSnark( - earlierProof.publicOutput.finalActionState, - [FundingAction.toFields(action)] - ); - - // Current value of the action hash should be NOT_EXISTED - let [root, key] = rollupStatusWitness.computeRootAndKey( - Field(ActionStatus.NOT_EXISTED) - ); - key.assertEquals(newActionState); - root.assertEquals(earlierProof.publicOutput.finalActionStatus); - - // New value of the action hash = REDUCED - [root] = rollupStatusWitness.computeRootAndKey( - Field(ActionStatus.REDUCED) - ); - - return new ReduceOutput({ - initialActionState: earlierProof.publicOutput.initialActionState, - initialActionStatus: earlierProof.publicOutput.initialActionStatus, - finalActionState: newActionState, - finalActionStatus: root, - }); - }, + name: 'create-rollup-status', + publicOutput: ReduceOutput, + methods: { + // First action to rollup + firstStep: { + privateInputs: [Field, Field], + method( + initialActionState: Field, + initialActionStatus: Field + ): ReduceOutput { + return new ReduceOutput({ + initialActionState, + initialActionStatus, + finalActionState: initialActionState, + finalActionStatus: initialActionStatus, + }); + }, + }, + // Next actions to rollup + nextStep: { + privateInputs: [ + SelfProof, + FundingAction, + MerkleMapWitness, + ], + method( + earlierProof: SelfProof, + action: FundingAction, + rollupStatusWitness: MerkleMapWitness + ): ReduceOutput { + // Verify earlier proof + earlierProof.verify(); + + // Calculate new action state == action id in the tree + let newActionState = updateOutOfSnark( + earlierProof.publicOutput.finalActionState, + [FundingAction.toFields(action)] + ); + + // Current value of the action hash should be NOT_EXISTED + let [root, key] = rollupStatusWitness.computeRootAndKey( + Field(ActionStatus.NOT_EXISTED) + ); + key.assertEquals(newActionState); + root.assertEquals(earlierProof.publicOutput.finalActionStatus); + + // New value of the action hash = REDUCED + [root] = rollupStatusWitness.computeRootAndKey( + Field(ActionStatus.REDUCED) + ); + + return new ReduceOutput({ + initialActionState: + earlierProof.publicOutput.initialActionState, + initialActionStatus: + earlierProof.publicOutput.initialActionStatus, + finalActionState: newActionState, + finalActionStatus: root, + }); + }, + }, }, - }, }); export class ReduceProof extends ZkProgram.Proof(CreateReduceProof) {} export class RollupActionsOutput extends Struct({ - campaignId: Field, - sum_R: DKG_Contracts.Request.RequestVector, - sum_M: DKG_Contracts.Request.RequestVector, - // TODO: make that we can increase number of investor - initialStatusRoot: Field, - finalStatusRoot: Field, + campaignId: Field, + sum_R: DKG_Contracts.Request.RequestVector, + sum_M: DKG_Contracts.Request.RequestVector, + // TODO: make that we can increase number of investor + initialStatusRoot: Field, + finalStatusRoot: Field, }) { - hash(): Field { - return Poseidon.hash(RollupActionsOutput.toFields(this)); - } + hash(): Field { + return Poseidon.hash(RollupActionsOutput.toFields(this)); + } } export const CreateRollupProof = ZkProgram({ - name: 'rollup-actions', - publicOutput: RollupActionsOutput, - methods: { - nextStep: { - privateInputs: [ - SelfProof, - FundingAction, - Field, - MerkleMapWitness, - ], - - method( - preProof: SelfProof, - action: FundingAction, - preActionState: Field, - rollupStatusWitness: MerkleMapWitness - ): RollupActionsOutput { - preProof.verify(); - let campaignId = action.campaignId; - campaignId.assertEquals(preProof.publicOutput.campaignId); - - let actionState = updateOutOfSnark(preActionState, [ - FundingAction.toFields(action), - ]); - - // It's status has to be REDUCED - let [root, key] = rollupStatusWitness.computeRootAndKey( - Field(ActionStatus.REDUCED) - ); - key.assertEquals(actionState); - root.assertEquals(preProof.publicOutput.finalStatusRoot); + name: 'rollup-actions', + publicOutput: RollupActionsOutput, + methods: { + nextStep: { + privateInputs: [ + SelfProof, + FundingAction, + Field, + MerkleMapWitness, + ], + + method( + preProof: SelfProof, + action: FundingAction, + preActionState: Field, + rollupStatusWitness: MerkleMapWitness + ): RollupActionsOutput { + preProof.verify(); + let campaignId = action.campaignId; + campaignId.assertEquals(preProof.publicOutput.campaignId); + + let actionState = updateOutOfSnark(preActionState, [ + FundingAction.toFields(action), + ]); + + // It's status has to be REDUCED + let [root, key] = rollupStatusWitness.computeRootAndKey( + Field(ActionStatus.REDUCED) + ); + key.assertEquals(actionState); + root.assertEquals(preProof.publicOutput.finalStatusRoot); + + // Update satus to ROLL_UPED + let [newRoot] = rollupStatusWitness.computeRootAndKey( + Field(ActionStatus.ROLL_UPED) + ); + + let sum_R = preProof.publicOutput.sum_R; + let sum_M = preProof.publicOutput.sum_M; + + for (let i = 0; i < DKG_Constants.REQUEST_MAX_SIZE; i++) { + sum_R.set( + Field(i), + sum_R.get(Field(i)).add(action.R.get(Field(i))) + ); + sum_M.set( + Field(i), + sum_M.get(Field(i)).add(action.M.get(Field(i))) + ); + } + + return new RollupActionsOutput({ + campaignId: campaignId, + sum_R, + sum_M, + initialStatusRoot: preProof.publicOutput.initialStatusRoot, + finalStatusRoot: newRoot, + }); + }, + }, + + firstStep: { + privateInputs: [Field, Field, Field], + + method( + campaignId: Field, + maxInvestorSize: Field, + initialStatusRoot: Field + ): RollupActionsOutput { + return new RollupActionsOutput({ + campaignId, + sum_R: DKG_Contracts.Request.RequestVector.empty( + maxInvestorSize + ), + sum_M: DKG_Contracts.Request.RequestVector.empty( + maxInvestorSize + ), + initialStatusRoot, + finalStatusRoot: initialStatusRoot, + }); + }, + }, + }, +}); - // Update satus to ROLL_UPED - let [newRoot] = rollupStatusWitness.computeRootAndKey( - Field(ActionStatus.ROLL_UPED) - ); +class ProofRollupAction extends ZkProgram.Proof(CreateRollupProof) {} - let sum_R = preProof.publicOutput.sum_R; - let sum_M = preProof.publicOutput.sum_M; +export class FundingContract extends SmartContract { + @state(Field) actionState = State(); + @state(Field) actionStatus = State(); + @state(Field) R_root = State(); // campaignId -> sum R + @state(Field) M_root = State(); // campaignId -> sum M + @state(Field) requestId_root = State(); // campaignId -> requestId + @state(Field) zkApps = State(); + + reducer = Reducer({ actionType: FundingAction }); + events = { + [EventEnum.ACTIONS_REDUCED]: Field, + [EventEnum.REQUEST_SENT]: RequestSent, + }; + + init() { + super.init(); + this.actionState.set(Reducer.initialActionState); + this.actionStatus.set(EMPTY_REDUCE_MT().getRoot()); + this.R_root.set(DefaultLevel1Root); + this.M_root.set(DefaultLevel1Root); + this.requestId_root.set(DefaultLevel1Root); + } + @method fund(fundingInput: FundingInput): { + R: DKG_Contracts.Request.RequestVector; + M: DKG_Contracts.Request.RequestVector; + } { + let dimension = fundingInput.secretVector.length; + let R = new DKG_Contracts.Request.RequestVector(); + let M = new DKG_Contracts.Request.RequestVector(); + // TODO: remove provable witness + let totalMinaInvest = Provable.witness(Field, () => { + let curSum = 0n; + for (let i = 0; i < dimension.toBigInt(); i++) { + curSum += fundingInput.secretVector + .get(Field(i)) + .toScalar() + .toBigInt(); + } + return Field(curSum); + }); for (let i = 0; i < DKG_Constants.REQUEST_MAX_SIZE; i++) { - sum_R.set(Field(i), sum_R.get(Field(i)).add(action.R.get(Field(i)))); - sum_M.set(Field(i), sum_M.get(Field(i)).add(action.M.get(Field(i)))); + let random = fundingInput.random.get(Field(i)).toScalar(); + R.push( + Provable.if( + Field(i).greaterThanOrEqual(dimension), + Group.fromFields([Field(0), Field(0)]), + Group.generator.scale(random) + ) + ); + // Trick to avoiding scale zero vector + + let tempSecretScalar = Provable.if( + fundingInput.secretVector + .get(Field(i)) + .equals(CustomScalar.fromScalar(Scalar.from(0n))), + CustomScalar, + CustomScalar.fromScalar(Scalar.from(69n)), // if equal zero + fundingInput.secretVector.get(Field(i)) + ); + + let M_i = Provable.if( + Poseidon.hash( + fundingInput.secretVector.get(Field(i)).toFields() + ).equals(Poseidon.hash([Field(0), Field(0)])), + Group.zero.add( + fundingInput.committeePublicKey.toGroup().scale(random) + ), + Group.generator + .scale(tempSecretScalar.toScalar()) + .add( + fundingInput.committeePublicKey.toGroup().scale(random) + ) + ); + + M_i = Provable.if( + Field(i) + .greaterThanOrEqual(dimension) + .or( + tempSecretScalar.equals( + CustomScalar.fromScalar(Scalar.from(69n)) + ) // if secret value equal zero then M_i is zero + ), + Group.fromFields([Field(0), Field(0)]), + M_i + ); + M.push(M_i); } + let dercementAmount = Field(DKG_Constants.REQUEST_MAX_SIZE).sub( + dimension + ); + R.decrementLength(dercementAmount); + M.decrementLength(dercementAmount); + + // Verify zkApp references + let zkApps = this.zkApps.getAndRequireEquals(); - return new RollupActionsOutput({ - campaignId: campaignId, - sum_R, - sum_M, - initialStatusRoot: preProof.publicOutput.initialStatusRoot, - finalStatusRoot: newRoot, + // TreasuryContract + zkApps.assertEquals( + fundingInput.treasuryContract.witness.calculateRoot( + Poseidon.hash(fundingInput.treasuryContract.address.toFields()) + ) + ); + Field(ZkAppEnum.TREASURY).assertEquals( + fundingInput.treasuryContract.witness.calculateIndex() + ); + + let investor = AccountUpdate.createSigned(this.sender); + // Send invest Mina to treasury contract + investor.send({ + to: AccountUpdate.create(fundingInput.treasuryContract.address), + amount: UInt64.from(totalMinaInvest), }); - }, - }, - firstStep: { - privateInputs: [Field, Field, Field], + this.reducer.dispatch( + new FundingAction({ + campaignId: fundingInput.campaignId, + R, + M, + }) + ); - method( - campaignId: Field, - maxInvestorSize: Field, - initialStatusRoot: Field - ): RollupActionsOutput { - return new RollupActionsOutput({ - campaignId, - sum_R: DKG_Contracts.Request.RequestVector.empty(maxInvestorSize), - sum_M: DKG_Contracts.Request.RequestVector.empty(maxInvestorSize), - initialStatusRoot, - finalStatusRoot: initialStatusRoot, + return { R, M }; + } + + @method reduce(proof: ReduceProof) { + // Verify proof + proof.verify(); + + // assert initialActionState + let actionState = this.actionState.getAndRequireEquals(); + proof.publicOutput.initialActionState.assertEquals(actionState); + + // assert initialActionStatus + let actionStatus = this.actionStatus.getAndRequireEquals(); + proof.publicOutput.initialActionStatus.assertEquals(actionStatus); + + // assert finalActionState + let lastActionState = this.account.actionState.getAndRequireEquals(); + lastActionState.assertEquals(proof.publicOutput.finalActionState); + + this.actionState.set(lastActionState); + this.actionStatus.set(proof.publicOutput.finalActionStatus); + + // Emit events + this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); + } + + // TODO: adding N, T to check REQUEST_MAX_SIZE by interact with Committee contract + // TODO: checking Campaign contract config -> committeeId and keyId + @method rollupRequest( + proof: ProofRollupAction, + committeeId: Field, + keyId: Field, + R_witness: Level1Witness, + M_witness: Level1Witness, + requestId_witness: Level1Witness, + requestZkAppRef: ZkAppRef + ) { + proof.verify(); + + let requestInput = new DKG_Contracts.Request.RequestInput({ + committeeId, + keyId, + R: proof.publicOutput.sum_R, }); - }, - }, - }, -}); -class ProofRollupAction extends ZkProgram.Proof(CreateRollupProof) {} + let requestId = requestInput.requestId(); + + let R_root = this.R_root.getAndRequireEquals(); + let M_root = this.M_root.getAndRequireEquals(); + let requestId_root = this.requestId_root.getAndRequireEquals(); + let actionStatus = this.actionStatus.getAndRequireEquals(); + + actionStatus.assertEquals(proof.publicOutput.initialStatusRoot); + let R_key = R_witness.calculateIndex(); + let old_R_root = R_witness.calculateRoot(Field(0)); + let M_key = M_witness.calculateIndex(); + let old_M_root = M_witness.calculateRoot(Field(0)); + let requestId_key = requestId_witness.calculateIndex(); + let old_requestId_root = requestId_witness.calculateRoot(Field(0)); + + R_key.assertEquals(proof.publicOutput.campaignId); + M_key.assertEquals(proof.publicOutput.campaignId); + requestId_key.assertEquals(proof.publicOutput.campaignId); + + R_root.assertEquals(old_R_root); + M_root.assertEquals(old_M_root); + requestId_root.assertEquals(old_requestId_root); + + // TODO: check number of investor + // maybe add time in the future + let new_R_root = R_witness.calculateRoot( + ValueStorage.calculateLeaf(proof.publicOutput.sum_R) + ); + let new_M_root = M_witness.calculateRoot( + ValueStorage.calculateLeaf(proof.publicOutput.sum_M) + ); + let new_requestId_root = requestId_witness.calculateRoot( + RequestIdStorage.calculateLeaf(requestId) + ); -export class FundingContract extends SmartContract { - @state(Field) actionState = State(); - @state(Field) actionStatus = State(); - @state(Field) R_root = State(); // campaignId -> sum R - @state(Field) M_root = State(); // campaignId -> sum M - @state(Field) requestId_root = State(); // campaignId -> requestId - @state(Field) zkApps = State(); - - reducer = Reducer({ actionType: FundingAction }); - events = { - [EventEnum.ACTIONS_REDUCED]: Field, - [EventEnum.REQUEST_SENT]: RequestSent, - }; - - init() { - super.init(); - this.actionState.set(Reducer.initialActionState); - this.actionStatus.set(EMPTY_REDUCE_MT().getRoot()); - this.R_root.set(DefaultLevel1Root); - this.M_root.set(DefaultLevel1Root); - this.requestId_root.set(DefaultLevel1Root); - } - - @method fund(fundingInput: FundingInput): { - R: DKG_Contracts.Request.RequestVector; - M: DKG_Contracts.Request.RequestVector; - } { - let dimension = fundingInput.secretVector.length; - let R = new DKG_Contracts.Request.RequestVector(); - let M = new DKG_Contracts.Request.RequestVector(); - // TODO: remove provable witness - let totalMinaInvest = Provable.witness(Field, () => { - let curSum = 0n; - for (let i = 0; i < dimension.toBigInt(); i++) { - curSum += fundingInput.secretVector.get(Field(i)).toScalar().toBigInt(); - } - return Field(curSum); - }); - for (let i = 0; i < DKG_Constants.REQUEST_MAX_SIZE; i++) { - let random = fundingInput.random.get(Field(i)).toScalar(); - R.push( - Provable.if( - Field(i).greaterThanOrEqual(dimension), - Group.fromFields([Field(0), Field(0)]), - Group.generator.scale(random) - ) - ); - // Trick to avoiding scale zero vector - - let tempSecretScalar = Provable.if( - fundingInput.secretVector - .get(Field(i)) - .equals(CustomScalar.fromScalar(Scalar.from(0n))), - CustomScalar, - CustomScalar.fromScalar(Scalar.from(69n)), // if equal zero - fundingInput.secretVector.get(Field(i)) - ); - - let M_i = Provable.if( - Poseidon.hash( - fundingInput.secretVector.get(Field(i)).toFields() - ).equals(Poseidon.hash([Field(0), Field(0)])), - Group.zero.add(fundingInput.committeePublicKey.toGroup().scale(random)), - Group.generator - .scale(tempSecretScalar.toScalar()) - .add(fundingInput.committeePublicKey.toGroup().scale(random)) - ); - - M_i = Provable.if( - Field(i) - .greaterThanOrEqual(dimension) - .or( - tempSecretScalar.equals(CustomScalar.fromScalar(Scalar.from(69n))) // if secret value equal zero then M_i is zero - ), - Group.fromFields([Field(0), Field(0)]), - M_i - ); - M.push(M_i); + // update on-chain state + this.R_root.set(new_R_root); + this.M_root.set(new_M_root); + this.requestId_root.set(new_requestId_root); + this.actionStatus.set(proof.publicOutput.finalStatusRoot); + + // TODO: request to Request contract + // Verify zkApp references + let zkApps = this.zkApps.getAndRequireEquals(); + + // TreasuryContract + zkApps.assertEquals( + requestZkAppRef.witness.calculateRoot( + Poseidon.hash(requestZkAppRef.address.toFields()) + ) + ); + Field(ZkAppEnum.REQUEST).assertEquals( + requestZkAppRef.witness.calculateIndex() + ); + + // Create & dispatch action to RequestContract + const requestZkApp = new DKG_Contracts.Request.RequestContract( + requestZkAppRef.address + ); + + requestZkApp.request(requestInput); + + this.emitEvent( + EventEnum.REQUEST_SENT, + new RequestSent({ + campaignId: proof.publicOutput.campaignId, + committeeId, + keyId, + requestId, + sumR: proof.publicOutput.sum_R, + sumM: proof.publicOutput.sum_M, + }) + ); } - let dercementAmount = Field(DKG_Constants.REQUEST_MAX_SIZE).sub(dimension); - R.decrementLength(dercementAmount); - M.decrementLength(dercementAmount); - - // Verify zkApp references - let zkApps = this.zkApps.getAndRequireEquals(); - - // TreasuryContract - zkApps.assertEquals( - fundingInput.treasuryContract.witness.calculateRoot( - Poseidon.hash(fundingInput.treasuryContract.address.toFields()) - ) - ); - Field(ZkAppEnum.TREASURY).assertEquals( - fundingInput.treasuryContract.witness.calculateIndex() - ); - - let investor = AccountUpdate.createSigned(this.sender); - // Send invest Mina to treasury contract - investor.send({ - to: AccountUpdate.create(fundingInput.treasuryContract.address), - amount: UInt64.from(totalMinaInvest), - }); - - this.reducer.dispatch( - new FundingAction({ - campaignId: fundingInput.campaignId, - R, - M, - }) - ); - - return { R, M }; - } - - @method reduce(proof: ReduceProof) { - // Verify proof - proof.verify(); - - // assert initialActionState - let actionState = this.actionState.getAndRequireEquals(); - proof.publicOutput.initialActionState.assertEquals(actionState); - - // assert initialActionStatus - let actionStatus = this.actionStatus.getAndRequireEquals(); - proof.publicOutput.initialActionStatus.assertEquals(actionStatus); - - // assert finalActionState - let lastActionState = this.account.actionState.getAndRequireEquals(); - lastActionState.assertEquals(proof.publicOutput.finalActionState); - - this.actionState.set(lastActionState); - this.actionStatus.set(proof.publicOutput.finalActionStatus); - - // Emit events - this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); - } - - // TODO: adding N, T to check REQUEST_MAX_SIZE by interact with Committee contract - // TODO: checking Campaign contract config -> committeeId and keyId - @method rollupRequest( - proof: ProofRollupAction, - committeeId: Field, - keyId: Field, - R_witness: Level1Witness, - M_witness: Level1Witness, - requestId_witness: Level1Witness, - requestZkAppRef: ZkAppRef - ) { - proof.verify(); - - let requestInput = new DKG_Contracts.Request.RequestInput({ - committeeId, - keyId, - R: proof.publicOutput.sum_R, - }); - - let requestId = requestInput.requestId(); - - let R_root = this.R_root.getAndRequireEquals(); - let M_root = this.M_root.getAndRequireEquals(); - let requestId_root = this.requestId_root.getAndRequireEquals(); - let actionStatus = this.actionStatus.getAndRequireEquals(); - - actionStatus.assertEquals(proof.publicOutput.initialStatusRoot); - let R_key = R_witness.calculateIndex(); - let old_R_root = R_witness.calculateRoot(Field(0)); - let M_key = M_witness.calculateIndex(); - let old_M_root = M_witness.calculateRoot(Field(0)); - let requestId_key = requestId_witness.calculateIndex(); - let old_requestId_root = requestId_witness.calculateRoot(Field(0)); - - R_key.assertEquals(proof.publicOutput.campaignId); - M_key.assertEquals(proof.publicOutput.campaignId); - requestId_key.assertEquals(proof.publicOutput.campaignId); - - R_root.assertEquals(old_R_root); - M_root.assertEquals(old_M_root); - requestId_root.assertEquals(old_requestId_root); - - // TODO: check number of investor - // maybe add time in the future - let new_R_root = R_witness.calculateRoot( - ValueStorage.calculateLeaf(proof.publicOutput.sum_R) - ); - let new_M_root = M_witness.calculateRoot( - ValueStorage.calculateLeaf(proof.publicOutput.sum_M) - ); - let new_requestId_root = requestId_witness.calculateRoot( - RequestIdStorage.calculateLeaf(requestId) - ); - - // update on-chain state - this.R_root.set(new_R_root); - this.M_root.set(new_M_root); - this.requestId_root.set(new_requestId_root); - this.actionStatus.set(proof.publicOutput.finalStatusRoot); - - // TODO: request to Request contract - // Verify zkApp references - let zkApps = this.zkApps.getAndRequireEquals(); - - // TreasuryContract - zkApps.assertEquals( - requestZkAppRef.witness.calculateRoot( - Poseidon.hash(requestZkAppRef.address.toFields()) - ) - ); - Field(ZkAppEnum.REQUEST).assertEquals( - requestZkAppRef.witness.calculateIndex() - ); - - // Create & dispatch action to RequestContract - const requestZkApp = new DKG_Contracts.Request.RequestContract( - requestZkAppRef.address - ); - - requestZkApp.request(requestInput); - - this.emitEvent( - EventEnum.REQUEST_SENT, - new RequestSent({ - campaignId: proof.publicOutput.campaignId, - committeeId, - keyId, - requestId, - sumR: proof.publicOutput.sum_R, - sumM: proof.publicOutput.sum_M, - }) - ); - } - - @method checkMvalue( - campaignId: Field, - M: DKG_Contracts.Request.RequestVector, - wintess: Level1Witness - ): Bool { - let isCorrect = Bool(true); - let caculateCampaignId = wintess.calculateIndex(); - isCorrect = isCorrect.and(campaignId.equals(caculateCampaignId)); + @method checkMvalue( + campaignId: Field, + M: DKG_Contracts.Request.RequestVector, + wintess: Level1Witness + ): Bool { + let isCorrect = Bool(true); - let M_root_on_chain = this.M_root.getAndRequireEquals(); - let calculateM = wintess.calculateRoot(ValueStorage.calculateLeaf(M)); - isCorrect = isCorrect.and(M_root_on_chain.equals(calculateM)); + let caculateCampaignId = wintess.calculateIndex(); + isCorrect = isCorrect.and(campaignId.equals(caculateCampaignId)); - return isCorrect; - } + let M_root_on_chain = this.M_root.getAndRequireEquals(); + let calculateM = wintess.calculateRoot(ValueStorage.calculateLeaf(M)); + isCorrect = isCorrect.and(M_root_on_chain.equals(calculateM)); - @method checkRvalue( - campaignId: Field, - R: DKG_Contracts.Request.RequestVector, - wintess: Level1Witness - ): Bool { - let isCorrect = Bool(true); + return isCorrect; + } + + @method checkRvalue( + campaignId: Field, + R: DKG_Contracts.Request.RequestVector, + wintess: Level1Witness + ): Bool { + let isCorrect = Bool(true); - let caculateCampaignId = wintess.calculateIndex(); - isCorrect = isCorrect.and(campaignId.equals(caculateCampaignId)); + let caculateCampaignId = wintess.calculateIndex(); + isCorrect = isCorrect.and(campaignId.equals(caculateCampaignId)); - let R_root_on_chain = this.R_root.getAndRequireEquals(); - let calculateR = wintess.calculateRoot(ValueStorage.calculateLeaf(R)); - isCorrect = isCorrect.and(R_root_on_chain.equals(calculateR)); + let R_root_on_chain = this.R_root.getAndRequireEquals(); + let calculateR = wintess.calculateRoot(ValueStorage.calculateLeaf(R)); + isCorrect = isCorrect.and(R_root_on_chain.equals(calculateR)); - return isCorrect; - } + return isCorrect; + } } diff --git a/src/contracts/FundingStorage.ts b/src/contracts/FundingStorage.ts index 71d0087..3f384c1 100644 --- a/src/contracts/FundingStorage.ts +++ b/src/contracts/FundingStorage.ts @@ -1,92 +1,122 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Field, MerkleTree, MerkleWitness, Poseidon } from 'o1js'; import { INSTANCE_LIMITS } from '../constants.js'; import { ZkApp } from '@auxo-dev/dkg'; + export const LEVEL_1_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; export class Level1MT extends MerkleTree {} export class Level1Witness extends MerkleWitness(LEVEL_1_TREE_HEIGHT) {} export const EMPTY_LEVEL_1_TREE = () => new Level1MT(LEVEL_1_TREE_HEIGHT); -// Storage -export abstract class FundingStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - this.level1 = level1 || EMPTY_LEVEL_1_TREE(); - } - - abstract calculateLeaf(args: any): Field; - abstract calculateLevel1Index(args: any): Field; - calculateLevel2Index?(args: any): Field; - - getLevel1Witness(level1Index: Field): Level1Witness { - return new Level1Witness(this.level1.getWitness(level1Index.toBigInt())); - } - - getWitness(level1Index: Field): Level1Witness { - return this.getLevel1Witness(level1Index); - } - - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class FundingStorage { + private _level1: Level1MT; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_TREE(); + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + leafs[i].level1Index, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + leafs[i].level1Index, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1Witness { + return new Level1Witness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getWitness(level1Index: Field): Level1Witness { + return this.getLevel1Witness(level1Index); + } + + updateLeaf(level1Index: Field, leaf: Field): void { + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf(level1Index: Field, rawLeaf: RawLeaf): void { + let leaf = this.calculateLeaf(rawLeaf); + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class ValueStorage extends FundingStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } - - calculateLeaf(value: ZkApp.Request.RequestVector): Field { - return ValueStorage.calculateLeaf(value); - } +export type ValueLeaf = ZkApp.Request.RequestVector; - static calculateLeaf(value: ZkApp.Request.RequestVector): Field { - return Poseidon.hash(value.toFields()); - } +export class ValueStorage extends FundingStorage { + static calculateLeaf(requestVecor: ValueLeaf): Field { + return Poseidon.hash(requestVecor.toFields()); + } - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } + calculateLeaf(requestVecor: ValueLeaf): Field { + return ValueStorage.calculateLeaf(requestVecor); + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(campaignId: Field): Field { + return ValueStorage.calculateLevel1Index(campaignId); + } } -export class RequestIdStorage extends FundingStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - super(level1); - } - - calculateLeaf(requestId: Field): Field { - return RequestIdStorage.calculateLeaf(requestId); - } +export type RequestIdLeaf = Field; - static calculateLeaf(requestId: Field): Field { - return requestId; - } +export class RequestIdStorage extends FundingStorage { + static calculateLeaf(requestId: RequestIdLeaf): Field { + return requestId; + } - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } + calculateLeaf(requestId: RequestIdLeaf): Field { + return RequestIdStorage.calculateLeaf(requestId); + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + calculateLevel1Index(campaignId: Field): Field { + return RequestIdStorage.calculateLevel1Index(campaignId); + } } diff --git a/src/contracts/Participation.ts b/src/contracts/Participation.ts index 89447c6..68d0d59 100644 --- a/src/contracts/Participation.ts +++ b/src/contracts/Participation.ts @@ -1,34 +1,34 @@ import { - Field, - SmartContract, - state, - State, - method, - Reducer, - Struct, - SelfProof, - Poseidon, - Provable, - ZkProgram, - PublicKey, - Void, - Bool, + Field, + SmartContract, + state, + State, + method, + Reducer, + Struct, + SelfProof, + Poseidon, + Provable, + ZkProgram, + PublicKey, + Void, + Bool, } from 'o1js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; import { updateOutOfSnark } from '../libs/utils.js'; import { - EMPTY_LEVEL_1_TREE, - EMPTY_LEVEL_1_COMBINED_TREE, - Level1CWitness as indexAndInfoWitness, - Level1Witness as counterWitness, - IndexStorage, - CounterStorage, - InfoStorage, + EMPTY_LEVEL_1_TREE, + EMPTY_LEVEL_1_COMBINED_TREE, + Level1CWitness as indexAndInfoWitness, + Level1Witness as counterWitness, + IndexStorage, + CounterStorage, + InfoStorage, } from './ParticipationStorage.js'; import { - Level1Witness as projectLv1Witness, - Level2Witness as projectLv2Witness, + Level1Witness as projectLv1Witness, + Level2Witness as projectLv2Witness, } from './ProjectStorage.js'; import { ProjectContract, CheckProjectOwerInput } from './Project.js'; @@ -40,315 +40,327 @@ const DefaultLevel1Root = EMPTY_LEVEL_1_TREE().getRoot(); const DefaultLevel1CombinedRoot = EMPTY_LEVEL_1_COMBINED_TREE().getRoot(); export enum EventEnum { - ACTIONS_REDUCED = 'actions-reduced', + ACTIONS_REDUCED = 'actions-reduced', } export class ParticipationAction extends Struct({ - campaignId: Field, - projectId: Field, - participationInfo: IPFSHash, - curApplicationInfoHash: Field, + campaignId: Field, + projectId: Field, + participationInfo: IPFSHash, + curApplicationInfoHash: Field, }) { - static fromFields(fields: Field[]): ParticipationAction { - return super.fromFields(fields) as ParticipationAction; - } + static fromFields(fields: Field[]): ParticipationAction { + return super.fromFields(fields) as ParticipationAction; + } - hashPIDandCID(): Field { - return Poseidon.hash([this.campaignId, this.projectId]); - } + hashPIDandCID(): Field { + return Poseidon.hash([this.campaignId, this.projectId]); + } } export class JoinCampaignInput extends Struct({ - campaignId: Field, - projectId: Field, - participationInfo: IPFSHash, - indexWitness: indexAndInfoWitness, - memberLv1Witness: projectLv1Witness, - memberLv2Witness: projectLv2Witness, - projectRef: ZkAppRef, + campaignId: Field, + projectId: Field, + participationInfo: IPFSHash, + indexWitness: indexAndInfoWitness, + memberLv1Witness: projectLv1Witness, + memberLv2Witness: projectLv2Witness, + projectRef: ZkAppRef, }) { - static fromFields(fields: Field[]): JoinCampaignInput { - return super.fromFields(fields) as JoinCampaignInput; - } + static fromFields(fields: Field[]): JoinCampaignInput { + return super.fromFields(fields) as JoinCampaignInput; + } } export class checkParticipationIndexInput extends Struct({ - campaignId: Field, - projectId: Field, - participationIndex: Field, - indexWitness: indexAndInfoWitness, + campaignId: Field, + projectId: Field, + participationIndex: Field, + indexWitness: indexAndInfoWitness, }) { - static fromFields(fields: Field[]): checkParticipationIndexInput { - return super.fromFields(fields) as checkParticipationIndexInput; - } + static fromFields(fields: Field[]): checkParticipationIndexInput { + return super.fromFields(fields) as checkParticipationIndexInput; + } } export class UpdateCampaignInput extends Struct({}) { - static fromFields(fields: Field[]): UpdateCampaignInput { - return super.fromFields(fields) as UpdateCampaignInput; - } + static fromFields(fields: Field[]): UpdateCampaignInput { + return super.fromFields(fields) as UpdateCampaignInput; + } } export class CreateParticipationProofOutput extends Struct({ - initialIndexTreeRoot: Field, - initialInfoTreeRoot: Field, - initialCounterTreeRoot: Field, - initialLastRolledUpACtionState: Field, - finalIndexTreeRoot: Field, - finalInfoTreeRoot: Field, - finalCounterTreeRoot: Field, - finalLastRolledUpActionState: Field, + initialIndexTreeRoot: Field, + initialInfoTreeRoot: Field, + initialCounterTreeRoot: Field, + initialLastRolledUpACtionState: Field, + finalIndexTreeRoot: Field, + finalInfoTreeRoot: Field, + finalCounterTreeRoot: Field, + finalLastRolledUpActionState: Field, }) { - hash(): Field { - return Poseidon.hash(CreateParticipationProofOutput.toFields(this)); - } + hash(): Field { + return Poseidon.hash(CreateParticipationProofOutput.toFields(this)); + } } export const JoinCampaign = ZkProgram({ - name: 'join-campaign', - publicOutput: CreateParticipationProofOutput, - methods: { - firstStep: { - privateInputs: [Field, Field, Field, Field], - method( - initialIndexTreeRoot: Field, - initialInfoTreeRoot: Field, - initialCounterTreeRoot: Field, - initialLastRolledUpACtionState: Field - ): CreateParticipationProofOutput { - return new CreateParticipationProofOutput({ - initialIndexTreeRoot, - initialInfoTreeRoot, - initialCounterTreeRoot, - initialLastRolledUpACtionState, - finalIndexTreeRoot: initialIndexTreeRoot, - finalInfoTreeRoot: initialInfoTreeRoot, - finalCounterTreeRoot: initialCounterTreeRoot, - finalLastRolledUpActionState: initialLastRolledUpACtionState, - }); - }, + name: 'join-campaign', + publicOutput: CreateParticipationProofOutput, + methods: { + firstStep: { + privateInputs: [Field, Field, Field, Field], + method( + initialIndexTreeRoot: Field, + initialInfoTreeRoot: Field, + initialCounterTreeRoot: Field, + initialLastRolledUpACtionState: Field + ): CreateParticipationProofOutput { + return new CreateParticipationProofOutput({ + initialIndexTreeRoot, + initialInfoTreeRoot, + initialCounterTreeRoot, + initialLastRolledUpACtionState, + finalIndexTreeRoot: initialIndexTreeRoot, + finalInfoTreeRoot: initialInfoTreeRoot, + finalCounterTreeRoot: initialCounterTreeRoot, + finalLastRolledUpActionState: + initialLastRolledUpACtionState, + }); + }, + }, + joinCampaign: { + privateInputs: [ + SelfProof, + ParticipationAction, + indexAndInfoWitness, + indexAndInfoWitness, + Field, + counterWitness, + ], + method( + preProof: SelfProof, + newAction: ParticipationAction, + indexWitness: indexAndInfoWitness, + infoWitness: indexAndInfoWitness, + currentCounter: Field, // of each campaign + counterWitness: counterWitness + ): CreateParticipationProofOutput { + preProof.verify(); + + // caculated index in storage tree, called id + let id = IndexStorage.calculateLevel1Index({ + campaignId: newAction.campaignId, + projectId: newAction.projectId, + }); + + // update counter + let counterId = counterWitness.calculateIndex(); + counterId.assertEquals(newAction.campaignId); + let curCounterTreeRoot = + counterWitness.calculateRoot(currentCounter); + curCounterTreeRoot.assertEquals( + preProof.publicOutput.finalCounterTreeRoot + ); + let newCounter = currentCounter.add(Field(1)); + let newCounterTreeRoot = + counterWitness.calculateRoot(newCounter); + + // update index + let indexId = indexWitness.calculateIndex(); + indexId.assertEquals(id); + let curIndexTreeRoot = indexWitness.calculateRoot(Field(0)); + curIndexTreeRoot.assertEquals( + preProof.publicOutput.finalIndexTreeRoot + ); + let newIndexTreeRoot = indexWitness.calculateRoot(newCounter); + + // update info-ipfs hash + let infoId = infoWitness.calculateIndex(); + infoId.assertEquals(id); + let curInfoTreeRoot = infoWitness.calculateRoot(Field(0)); + curInfoTreeRoot.assertEquals( + preProof.publicOutput.finalInfoTreeRoot + ); + let newInfoTreeRoot = infoWitness.calculateRoot( + InfoStorage.calculateLeaf(newAction.participationInfo) + ); + + return new CreateParticipationProofOutput({ + initialIndexTreeRoot: + preProof.publicOutput.initialIndexTreeRoot, + initialInfoTreeRoot: + preProof.publicOutput.initialInfoTreeRoot, + initialCounterTreeRoot: + preProof.publicOutput.initialCounterTreeRoot, + initialLastRolledUpACtionState: + preProof.publicOutput.initialLastRolledUpACtionState, + finalIndexTreeRoot: newIndexTreeRoot, + finalCounterTreeRoot: newCounterTreeRoot, + finalInfoTreeRoot: newInfoTreeRoot, + finalLastRolledUpActionState: updateOutOfSnark( + preProof.publicOutput.finalLastRolledUpActionState, + [ParticipationAction.toFields(newAction)] + ), + }); + }, + }, }, - joinCampaign: { - privateInputs: [ - SelfProof, - ParticipationAction, - indexAndInfoWitness, - indexAndInfoWitness, - Field, - counterWitness, - ], - method( - preProof: SelfProof, - newAction: ParticipationAction, - indexWitness: indexAndInfoWitness, - infoWitness: indexAndInfoWitness, - currentCounter: Field, // of each campaign - counterWitness: counterWitness - ): CreateParticipationProofOutput { - preProof.verify(); - - // caculated index in storage tree, called id - let id = IndexStorage.calculateLevel1Index({ - campaignId: newAction.campaignId, - projectId: newAction.projectId, +}); + +class ParticipationProof extends ZkProgram.Proof(JoinCampaign) {} + +export class ParticipationContract extends SmartContract { + // campaignId -> projectId -> index. start from 1, if index = 0 means that project have not participate + @state(Field) indexTreeRoot = State(); + // campaignId -> projectId -> index + @state(Field) infoTreeRoot = State(); + // campaignId -> counter + @state(Field) counterTreeRoot = State(); + // MT of other zkApp address + @state(Field) zkApps = State(); + @state(Field) lastRolledUpActionState = State(); + + reducer = Reducer({ actionType: ParticipationAction }); + events = { + [EventEnum.ACTIONS_REDUCED]: Field, + }; + + init() { + super.init(); + this.indexTreeRoot.set(DefaultLevel1CombinedRoot); + this.infoTreeRoot.set(DefaultLevel1CombinedRoot); + this.counterTreeRoot.set(DefaultLevel1Root); + this.lastRolledUpActionState.set(Reducer.initialActionState); + } + + @method joinCampaign(input: JoinCampaignInput) { + // TODO: check campaign status + + // check owner + let zkApps = this.zkApps.getAndRequireEquals(); + + // check project contract + zkApps.assertEquals( + input.projectRef.witness.calculateRoot( + Poseidon.hash(input.projectRef.address.toFields()) + ) + ); + + Field(ZkAppEnum.PROJECT).assertEquals( + input.projectRef.witness.calculateIndex() + ); + + let projectContract = new ProjectContract(input.projectRef.address); + + // TODO: check latter + // let isOwner = projectContract.checkProjectOwner( + // new CheckProjectOwerInput({ + // owner: this.sender, + // projectId: input.projectId, + // memberLevel1Witness: input.memberLv1Witness, + // memberLevel2Witness: input.memberLv2Witness, + // }) + // ); + // isOwner.assertEquals(Bool(true)); + + // check if this is first time join campaign + + let notIn = this.checkParticipationIndex( + new checkParticipationIndexInput({ + campaignId: input.campaignId, + projectId: input.projectId, + participationIndex: Field(0), + indexWitness: input.indexWitness, + }) + ); + + notIn.assertEquals(Bool(true)); + + // each project can only participate campaign once + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + let newAction = new ParticipationAction({ + campaignId: input.campaignId, + projectId: input.projectId, + participationInfo: input.participationInfo, + curApplicationInfoHash: Field(0), }); - // update counter - let counterId = counterWitness.calculateIndex(); - counterId.assertEquals(newAction.campaignId); - let curCounterTreeRoot = counterWitness.calculateRoot(currentCounter); - curCounterTreeRoot.assertEquals( - preProof.publicOutput.finalCounterTreeRoot + let chekcHash = newAction.hashPIDandCID(); + + // TODO: not really able to do this, check again. If both of them send at the same block + // checking if the request have the same id already exists within the accumulator + let { state: exists } = this.reducer.reduce( + this.reducer.getActions({ + fromActionState: lastRolledUpActionState, + }), + Bool, + (state: Bool, action: ParticipationAction) => { + return action.hashPIDandCID().equals(chekcHash).or(state); + }, + // initial state + { state: Bool(false), actionState: lastRolledUpActionState } ); - let newCounter = currentCounter.add(Field(1)); - let newCounterTreeRoot = counterWitness.calculateRoot(newCounter); - - // update index - let indexId = indexWitness.calculateIndex(); - indexId.assertEquals(id); - let curIndexTreeRoot = indexWitness.calculateRoot(Field(0)); - curIndexTreeRoot.assertEquals(preProof.publicOutput.finalIndexTreeRoot); - let newIndexTreeRoot = indexWitness.calculateRoot(newCounter); - - // update info-ipfs hash - let infoId = infoWitness.calculateIndex(); - infoId.assertEquals(id); - let curInfoTreeRoot = infoWitness.calculateRoot(Field(0)); - curInfoTreeRoot.assertEquals(preProof.publicOutput.finalInfoTreeRoot); - let newInfoTreeRoot = infoWitness.calculateRoot( - InfoStorage.calculateLeaf(newAction.participationInfo) + + // if exists then don't dispatch any more + exists.assertEquals(Bool(false)); + + this.reducer.dispatch(newAction); + } + + @method rollup(proof: ParticipationProof) { + proof.verify(); + let indexTreeRoot = this.indexTreeRoot.getAndRequireEquals(); + let infoTreeRoot = this.infoTreeRoot.getAndRequireEquals(); + let counterTreeRoot = this.counterTreeRoot.getAndRequireEquals(); + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + indexTreeRoot.assertEquals(proof.publicOutput.initialIndexTreeRoot); + infoTreeRoot.assertEquals(proof.publicOutput.initialInfoTreeRoot); + counterTreeRoot.assertEquals(proof.publicOutput.initialCounterTreeRoot); + lastRolledUpActionState.assertEquals( + proof.publicOutput.initialLastRolledUpACtionState + ); + + let lastActionState = this.account.actionState.getAndRequireEquals(); + lastActionState.assertEquals( + proof.publicOutput.finalLastRolledUpActionState ); - return new CreateParticipationProofOutput({ - initialIndexTreeRoot: preProof.publicOutput.initialIndexTreeRoot, - initialInfoTreeRoot: preProof.publicOutput.initialInfoTreeRoot, - initialCounterTreeRoot: preProof.publicOutput.initialCounterTreeRoot, - initialLastRolledUpACtionState: - preProof.publicOutput.initialLastRolledUpACtionState, - finalIndexTreeRoot: newIndexTreeRoot, - finalCounterTreeRoot: newCounterTreeRoot, - finalInfoTreeRoot: newInfoTreeRoot, - finalLastRolledUpActionState: updateOutOfSnark( - preProof.publicOutput.finalLastRolledUpActionState, - [ParticipationAction.toFields(newAction)] - ), + // update on-chain state + this.indexTreeRoot.set(proof.publicOutput.finalIndexTreeRoot); + this.infoTreeRoot.set(proof.publicOutput.finalInfoTreeRoot); + this.counterTreeRoot.set(proof.publicOutput.finalCounterTreeRoot); + this.lastRolledUpActionState.set( + proof.publicOutput.finalLastRolledUpActionState + ); + + this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); + } + + @method checkParticipationIndex(input: checkParticipationIndexInput): Bool { + let isValid = Bool(true); + + let index = IndexStorage.calculateLevel1Index({ + campaignId: input.campaignId, + projectId: input.projectId, }); - }, - }, - }, -}); -class ParticipationProof extends ZkProgram.Proof(JoinCampaign) {} + // check the right projectId + let calculateIndex = input.indexWitness.calculateIndex(); + isValid = index.equals(calculateIndex).and(isValid); -export class ParticipationContract extends SmartContract { - // campaignId -> projectId -> index. start from 1, if index = 0 means that project have not participate - @state(Field) indexTreeRoot = State(); - // campaignId -> projectId -> index - @state(Field) infoTreeRoot = State(); - // campaignId -> counter - @state(Field) counterTreeRoot = State(); - // MT of other zkApp address - @state(Field) zkApps = State(); - @state(Field) lastRolledUpActionState = State(); - - reducer = Reducer({ actionType: ParticipationAction }); - events = { - [EventEnum.ACTIONS_REDUCED]: Field, - }; - - init() { - super.init(); - this.indexTreeRoot.set(DefaultLevel1CombinedRoot); - this.infoTreeRoot.set(DefaultLevel1CombinedRoot); - this.counterTreeRoot.set(DefaultLevel1Root); - this.lastRolledUpActionState.set(Reducer.initialActionState); - } - - @method joinCampaign(input: JoinCampaignInput) { - // TODO: check campaign status - - // check owner - let zkApps = this.zkApps.getAndRequireEquals(); - - // check project contract - zkApps.assertEquals( - input.projectRef.witness.calculateRoot( - Poseidon.hash(input.projectRef.address.toFields()) - ) - ); - - Field(ZkAppEnum.PROJECT).assertEquals( - input.projectRef.witness.calculateIndex() - ); - - let projectContract = new ProjectContract(input.projectRef.address); - - // TODO: check latter - // let isOwner = projectContract.checkProjectOwner( - // new CheckProjectOwerInput({ - // owner: this.sender, - // projectId: input.projectId, - // memberLevel1Witness: input.memberLv1Witness, - // memberLevel2Witness: input.memberLv2Witness, - // }) - // ); - // isOwner.assertEquals(Bool(true)); - - // check if this is first time join campaign - - let notIn = this.checkParticipationIndex( - new checkParticipationIndexInput({ - campaignId: input.campaignId, - projectId: input.projectId, - participationIndex: Field(0), - indexWitness: input.indexWitness, - }) - ); - - notIn.assertEquals(Bool(true)); - - // each project can only participate campaign once - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - let newAction = new ParticipationAction({ - campaignId: input.campaignId, - projectId: input.projectId, - participationInfo: input.participationInfo, - curApplicationInfoHash: Field(0), - }); - - let chekcHash = newAction.hashPIDandCID(); - - // TODO: not really able to do this, check again. If both of them send at the same block - // checking if the request have the same id already exists within the accumulator - let { state: exists } = this.reducer.reduce( - this.reducer.getActions({ - fromActionState: lastRolledUpActionState, - }), - Bool, - (state: Bool, action: ParticipationAction) => { - return action.hashPIDandCID().equals(chekcHash).or(state); - }, - // initial state - { state: Bool(false), actionState: lastRolledUpActionState } - ); - - // if exists then don't dispatch any more - exists.assertEquals(Bool(false)); - - this.reducer.dispatch(newAction); - } - - @method rollup(proof: ParticipationProof) { - proof.verify(); - let indexTreeRoot = this.indexTreeRoot.getAndRequireEquals(); - let infoTreeRoot = this.infoTreeRoot.getAndRequireEquals(); - let counterTreeRoot = this.counterTreeRoot.getAndRequireEquals(); - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - indexTreeRoot.assertEquals(proof.publicOutput.initialIndexTreeRoot); - infoTreeRoot.assertEquals(proof.publicOutput.initialInfoTreeRoot); - counterTreeRoot.assertEquals(proof.publicOutput.initialCounterTreeRoot); - lastRolledUpActionState.assertEquals( - proof.publicOutput.initialLastRolledUpACtionState - ); - - let lastActionState = this.account.actionState.getAndRequireEquals(); - lastActionState.assertEquals( - proof.publicOutput.finalLastRolledUpActionState - ); - - // update on-chain state - this.indexTreeRoot.set(proof.publicOutput.finalIndexTreeRoot); - this.infoTreeRoot.set(proof.publicOutput.finalInfoTreeRoot); - this.counterTreeRoot.set(proof.publicOutput.finalCounterTreeRoot); - this.lastRolledUpActionState.set( - proof.publicOutput.finalLastRolledUpActionState - ); - - this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); - } - - @method checkParticipationIndex(input: checkParticipationIndexInput): Bool { - let isValid = Bool(true); - - let index = IndexStorage.calculateLevel1Index({ - campaignId: input.campaignId, - projectId: input.projectId, - }); - - // check the right projectId - let calculateIndex = input.indexWitness.calculateIndex(); - isValid = index.equals(calculateIndex).and(isValid); - - // check the valid of the index - let level1Root = input.indexWitness.calculateRoot(input.participationIndex); - isValid = level1Root - .equals(this.indexTreeRoot.getAndRequireEquals()) - .and(isValid); - - return isValid; - } + // check the valid of the index + let level1Root = input.indexWitness.calculateRoot( + input.participationIndex + ); + isValid = level1Root + .equals(this.indexTreeRoot.getAndRequireEquals()) + .and(isValid); + + return isValid; + } } diff --git a/src/contracts/ParticipationStorage.ts b/src/contracts/ParticipationStorage.ts index e52c96b..a03e7c9 100644 --- a/src/contracts/ParticipationStorage.ts +++ b/src/contracts/ParticipationStorage.ts @@ -3,139 +3,247 @@ import { INSTANCE_LIMITS } from '../constants.js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; export const LEVEL_1_COMBINED_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN * INSTANCE_LIMITS.PROJECT)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN * INSTANCE_LIMITS.PROJECT)) + + 1; export const LEVEL_1_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN)) + 1; export class Level1CMT extends MerkleTree {} export class Level1CWitness extends MerkleWitness( - LEVEL_1_COMBINED_TREE_HEIGHT + LEVEL_1_COMBINED_TREE_HEIGHT ) {} export class Level1MT extends MerkleTree {} export class Level1Witness extends MerkleWitness(LEVEL_1_TREE_HEIGHT) {} export const EMPTY_LEVEL_1_COMBINED_TREE = () => - new Level1CMT(LEVEL_1_COMBINED_TREE_HEIGHT); + new Level1CMT(LEVEL_1_COMBINED_TREE_HEIGHT); export const EMPTY_LEVEL_1_TREE = () => new Level1MT(LEVEL_1_TREE_HEIGHT); -// Storage -export class IndexStorage { - level1: Level1CMT; - - constructor(level1?: Level1CMT) { - this.level1 = level1 || EMPTY_LEVEL_1_COMBINED_TREE(); - } - - calculateLeaf(index: Field): Field { - return IndexStorage.calculateLeaf(index); - } - - static calculateLeaf(index: Field): Field { - return index; - } - - calculateLevel1Index({ - campaignId, - projectId, - }: { - campaignId: Field; - projectId: Field; - }): Field { - return IndexStorage.calculateLevel1Index({ campaignId, projectId }); - } - - static calculateLevel1Index({ - campaignId, - projectId, - }: { - campaignId: Field; - projectId: Field; - }): Field { - return campaignId.mul(INSTANCE_LIMITS.PROJECT).add(projectId); - } - - getLevel1Witness(level1Index: Field): Level1CWitness { - return new Level1CWitness(this.level1.getWitness(level1Index.toBigInt())); - } - - getWitness(level1Index: Field): Level1CWitness { - return this.getLevel1Witness(level1Index); - } - - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class ParticipationCStorage { + private _level1: Level1CMT; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_COMBINED_TREE(); + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + leafs[i].level1Index, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + leafs[i].level1Index, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1Witness { + return new Level1Witness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getWitness(level1Index: Field): Level1Witness { + return this.getLevel1Witness(level1Index); + } + + updateLeaf(level1Index: Field, leaf: Field): void { + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf(level1Index: Field, rawLeaf: RawLeaf): void { + let leaf = this.calculateLeaf(rawLeaf); + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class InfoStorage { - level1: Level1CMT; - - constructor(level1?: Level1CMT) { - this.level1 = level1 || EMPTY_LEVEL_1_COMBINED_TREE(); - } - - calculateLeaf(ipfshash: IPFSHash): Field { - return InfoStorage.calculateLeaf(ipfshash); - } - - static calculateLeaf(ipfshash: IPFSHash): Field { - return Poseidon.hash(ipfshash.toFields()); - } - - calculateLevel1Index({ - campaignId, - projectId, - }: { - campaignId: Field; - projectId: Field; - }): Field { - return campaignId.mul(INSTANCE_LIMITS.PROJECT).add(projectId); - } - - getLevel1Witness(level1Index: Field): Level1CWitness { - return new Level1CWitness(this.level1.getWitness(level1Index.toBigInt())); - } - - getWitness(level1Index: Field): Level1CWitness { - return this.getLevel1Witness(level1Index); - } - - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class ParticipationStorage { + private _level1: Level1MT; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_TREE(); + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + leafs[i].level1Index, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + leafs[i].level1Index, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1Witness { + return new Level1Witness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getWitness(level1Index: Field): Level1Witness { + return this.getLevel1Witness(level1Index); + } + + updateLeaf(level1Index: Field, leaf: Field): void { + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf(level1Index: Field, rawLeaf: RawLeaf): void { + let leaf = this.calculateLeaf(rawLeaf); + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class CounterStorage { - level1: Level1MT; - - constructor(level1?: Level1MT) { - this.level1 = level1 || EMPTY_LEVEL_1_TREE(); - } +export type IndexLeaf = Field; + +export class IndexStorage extends ParticipationCStorage { + static calculateLeaf(index: IndexLeaf): Field { + return index; + } + + calculateLeaf(index: IndexLeaf): Field { + return IndexStorage.calculateLeaf(index); + } + + static calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return campaignId.mul(INSTANCE_LIMITS.PROJECT).add(projectId); + } + + calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return IndexStorage.calculateLevel1Index({ campaignId, projectId }); + } +} - calculateLeaf(counter: Field): Field { - return CounterStorage.calculateLeaf(counter); - } +export type InfoLeaf = IPFSHash; + +export class InfoStorage extends ParticipationCStorage { + static calculateLeaf(ipfsHash: InfoLeaf): Field { + return Poseidon.hash(ipfsHash.toFields()); + } + + calculateLeaf(ipfsHash: InfoLeaf): Field { + return InfoStorage.calculateLeaf(ipfsHash); + } + + static calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return campaignId.mul(INSTANCE_LIMITS.PROJECT).add(projectId); + } + + calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return InfoStorage.calculateLevel1Index({ campaignId, projectId }); + } +} - static calculateLeaf(counter: Field): Field { - return counter; - } +export type CounterLeaf = Field; - calculateLevel1Index(campaignId: Field): Field { - return campaignId; - } +export class CounterStorage extends ParticipationStorage { + static calculateLeaf(counter: CounterLeaf): Field { + return counter; + } - getLevel1Witness(level1Index: Field): Level1Witness { - return new Level1Witness(this.level1.getWitness(level1Index.toBigInt())); - } + calculateLeaf(counter: CounterLeaf): Field { + return CounterStorage.calculateLeaf(counter); + } - getWitness(level1Index: Field): Level1Witness { - return this.getLevel1Witness(level1Index); - } + static calculateLevel1Index(campaignId: Field): Field { + return campaignId; + } - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } + calculateLevel1Index(campaignId: Field): Field { + return CounterStorage.calculateLevel1Index(campaignId); + } } diff --git a/src/contracts/Project.ts b/src/contracts/Project.ts index 36a2ce7..000d3bb 100644 --- a/src/contracts/Project.ts +++ b/src/contracts/Project.ts @@ -1,364 +1,381 @@ import { - Field, - SmartContract, - state, - State, - method, - Reducer, - Struct, - SelfProof, - Poseidon, - Provable, - ZkProgram, - PublicKey, - Void, - Bool, + Field, + SmartContract, + state, + State, + method, + Reducer, + Struct, + SelfProof, + Poseidon, + Provable, + ZkProgram, + PublicKey, + Void, + Bool, } from 'o1js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; import { updateOutOfSnark } from '../libs/utils.js'; import { PROJECT_MEMBER_MAX_SIZE } from '../constants.js'; import { - EMPTY_LEVEL_1_TREE, - EMPTY_LEVEL_2_TREE, - Level1Witness, - Level2Witness, - FullMTWitness, - MemberArray, - InfoStorage, - AddressStorage, + EMPTY_LEVEL_1_TREE, + EMPTY_LEVEL_2_TREE, + Level1Witness, + Level2Witness, + FullMTWitness, + MemberArray, + InfoStorage, + AddressStorage, } from './ProjectStorage.js'; const DefaultLevel1Root = EMPTY_LEVEL_1_TREE().getRoot(); export class ProjectAction extends Struct({ - projectId: Field, - members: MemberArray, - ipfsHash: IPFSHash, - payeeAccount: PublicKey, + projectId: Field, + members: MemberArray, + ipfsHash: IPFSHash, + payeeAccount: PublicKey, }) { - static fromFields(fields: Field[]): ProjectAction { - return super.fromFields(fields) as ProjectAction; - } + static fromFields(fields: Field[]): ProjectAction { + return super.fromFields(fields) as ProjectAction; + } } export class CheckProjectOwerInput extends Struct({ - owner: PublicKey, - projectId: Field, - memberLevel1Witness: Level1Witness, - memberLevel2Witness: Level2Witness, + owner: PublicKey, + projectId: Field, + memberLevel1Witness: Level1Witness, + memberLevel2Witness: Level2Witness, }) {} export class CreateProjectInput extends Struct({ - members: MemberArray, - ipfsHash: IPFSHash, - payeeAccount: PublicKey, + members: MemberArray, + ipfsHash: IPFSHash, + payeeAccount: PublicKey, }) { - static fromFields(fields: Field[]): CreateProjectInput { - return super.fromFields(fields) as CreateProjectInput; - } + static fromFields(fields: Field[]): CreateProjectInput { + return super.fromFields(fields) as CreateProjectInput; + } } export class UpdateProjectInput extends Struct({ - projectId: Field, - members: MemberArray, - ipfsHash: IPFSHash, - payeeAccount: PublicKey, - memberLevel1Witness: Level1Witness, - memberLevel2Witness: Level2Witness, + projectId: Field, + members: MemberArray, + ipfsHash: IPFSHash, + payeeAccount: PublicKey, + memberLevel1Witness: Level1Witness, + memberLevel2Witness: Level2Witness, }) { - static fromFields(fields: Field[]): UpdateProjectInput { - return super.fromFields(fields) as UpdateProjectInput; - } + static fromFields(fields: Field[]): UpdateProjectInput { + return super.fromFields(fields) as UpdateProjectInput; + } } export class CreateProjectProofOutput extends Struct({ - initialNextProjectId: Field, - initialMemberTreeRoot: Field, - initialProjectInfoTreeRoot: Field, - initialPayeeTreeRoot: Field, - initialLastRolledUpActionState: Field, - finalNextProjectId: Field, - finalMemberTreeRoot: Field, - finalProjectInfoTreeRoot: Field, - finalPayeeTreeRoot: Field, - finalLastRolledUpActionState: Field, + initialNextProjectId: Field, + initialMemberTreeRoot: Field, + initialProjectInfoTreeRoot: Field, + initialPayeeTreeRoot: Field, + initialLastRolledUpActionState: Field, + finalNextProjectId: Field, + finalMemberTreeRoot: Field, + finalProjectInfoTreeRoot: Field, + finalPayeeTreeRoot: Field, + finalLastRolledUpActionState: Field, }) { - hash(): Field { - return Poseidon.hash(CreateProjectProofOutput.toFields(this)); - } + hash(): Field { + return Poseidon.hash(CreateProjectProofOutput.toFields(this)); + } } export const CreateProject = ZkProgram({ - name: 'create-project', - publicOutput: CreateProjectProofOutput, - methods: { - firstStep: { - privateInputs: [Field, Field, Field, Field, Field], - method( - initialNextProjectId: Field, - initialMemberTreeRoot: Field, - initialProjectInfoTreeRoot: Field, - initialPayeeTreeRoot: Field, - initialLastRolledUpActionState: Field - ): CreateProjectProofOutput { - return new CreateProjectProofOutput({ - initialNextProjectId, - initialMemberTreeRoot, - initialProjectInfoTreeRoot, - initialPayeeTreeRoot, - initialLastRolledUpActionState, - finalNextProjectId: initialNextProjectId, - finalMemberTreeRoot: initialMemberTreeRoot, - finalProjectInfoTreeRoot: initialProjectInfoTreeRoot, - finalPayeeTreeRoot: initialPayeeTreeRoot, - finalLastRolledUpActionState: initialLastRolledUpActionState, - }); - }, + name: 'create-project', + publicOutput: CreateProjectProofOutput, + methods: { + firstStep: { + privateInputs: [Field, Field, Field, Field, Field], + method( + initialNextProjectId: Field, + initialMemberTreeRoot: Field, + initialProjectInfoTreeRoot: Field, + initialPayeeTreeRoot: Field, + initialLastRolledUpActionState: Field + ): CreateProjectProofOutput { + return new CreateProjectProofOutput({ + initialNextProjectId, + initialMemberTreeRoot, + initialProjectInfoTreeRoot, + initialPayeeTreeRoot, + initialLastRolledUpActionState, + finalNextProjectId: initialNextProjectId, + finalMemberTreeRoot: initialMemberTreeRoot, + finalProjectInfoTreeRoot: initialProjectInfoTreeRoot, + finalPayeeTreeRoot: initialPayeeTreeRoot, + finalLastRolledUpActionState: + initialLastRolledUpActionState, + }); + }, + }, + nextStep: { + privateInputs: [ + SelfProof, + ProjectAction, + Level1Witness, + Level1Witness, + Level1Witness, + ], + method( + preProof: SelfProof, + newAction: ProjectAction, + memberWitness: Level1Witness, + projectInfoWitess: Level1Witness, + payeeWitness: Level1Witness + ): CreateProjectProofOutput { + preProof.verify(); + + // check if create project + let isCreateProject = newAction.projectId.equals(Field(-1)); + // if create project: newProjectId = next project id + // if update project: newProjectId = newAction projectId + let newProjectId = Provable.if( + isCreateProject, + preProof.publicOutput.finalNextProjectId, + newAction.projectId + ); + + let nextProjectId = Provable.if( + isCreateProject, + preProof.publicOutput.finalNextProjectId.add(Field(1)), + preProof.publicOutput.finalNextProjectId + ); + + ////// caculate new memberTreeRoot + let newProjectIndex = memberWitness.calculateIndex(); + let preMemberRoot = memberWitness.calculateRoot(Field(0)); + newProjectId.assertEquals(newProjectIndex); + preMemberRoot.assertEquals( + preProof.publicOutput.finalMemberTreeRoot + ); + + let tree = EMPTY_LEVEL_2_TREE(); + for (let i = 0; i < PROJECT_MEMBER_MAX_SIZE; i++) { + let value = Provable.if( + Field(i).greaterThanOrEqual(newAction.members.length), + Field(0), + MemberArray.hash(newAction.members.get(Field(i))) + ); + tree.setLeaf(BigInt(i), value); + } + + // update new member tree + let newMemberTreeRoot = memberWitness.calculateRoot( + tree.getRoot() + ); + + ////// caculate new projectInfoTreeRoot + let preProjectInfoRoot = projectInfoWitess.calculateRoot( + Field(0) + ); + let projectInfoIndex = projectInfoWitess.calculateIndex(); + projectInfoIndex.assertEquals(newProjectId); + preProjectInfoRoot.assertEquals( + preProof.publicOutput.finalProjectInfoTreeRoot + ); + + // update project info tree with hash ipfs hash + let newProjectInfoTreeRoot = projectInfoWitess.calculateRoot( + InfoStorage.calculateLeaf(newAction.ipfsHash) + ); + + ////// caculate new addressTreeRoot + let prePayeeTreeRoot = payeeWitness.calculateRoot(Field(0)); + let addressIndex = payeeWitness.calculateIndex(); + addressIndex.assertEquals(newProjectId); + prePayeeTreeRoot.assertEquals( + preProof.publicOutput.finalPayeeTreeRoot + ); + + // update project info tree with hash ipfs hash + let newPayeeTreeRoot = payeeWitness.calculateRoot( + AddressStorage.calculateLeaf(newAction.payeeAccount) + ); + + return new CreateProjectProofOutput({ + initialNextProjectId: + preProof.publicOutput.initialNextProjectId, + initialMemberTreeRoot: + preProof.publicOutput.initialMemberTreeRoot, + initialProjectInfoTreeRoot: + preProof.publicOutput.initialProjectInfoTreeRoot, + initialPayeeTreeRoot: + preProof.publicOutput.initialPayeeTreeRoot, + initialLastRolledUpActionState: + preProof.publicOutput.initialLastRolledUpActionState, + finalNextProjectId: nextProjectId, + finalMemberTreeRoot: newMemberTreeRoot, + finalProjectInfoTreeRoot: newProjectInfoTreeRoot, + finalPayeeTreeRoot: newPayeeTreeRoot, + finalLastRolledUpActionState: updateOutOfSnark( + preProof.publicOutput.finalLastRolledUpActionState, + [ProjectAction.toFields(newAction)] + ), + }); + }, + }, }, - nextStep: { - privateInputs: [ - SelfProof, - ProjectAction, - Level1Witness, - Level1Witness, - Level1Witness, - ], - method( - preProof: SelfProof, - newAction: ProjectAction, - memberWitness: Level1Witness, - projectInfoWitess: Level1Witness, - payeeWitness: Level1Witness - ): CreateProjectProofOutput { - preProof.verify(); - - // check if create project - let isCreateProject = newAction.projectId.equals(Field(-1)); - // if create project: newProjectId = next project id - // if update project: newProjectId = newAction projectId - let newProjectId = Provable.if( - isCreateProject, - preProof.publicOutput.finalNextProjectId, - newAction.projectId - ); +}); + +export class ProjectProof extends ZkProgram.Proof(CreateProject) {} + +export enum EventEnum { + PROJECT_CREATED = 'project-created', +} - let nextProjectId = Provable.if( - isCreateProject, - preProof.publicOutput.finalNextProjectId.add(Field(1)), - preProof.publicOutput.finalNextProjectId +export class ProjectContract extends SmartContract { + @state(Field) nextProjectId = State(); + @state(Field) memberTreeRoot = State(); + @state(Field) projectInfoTreeRoot = State(); + @state(Field) payeeTreeRoot = State(); + @state(Field) lastRolledUpActionState = State(); + + reducer = Reducer({ actionType: ProjectAction }); + + events = { + [EventEnum.PROJECT_CREATED]: Field, + }; + + init() { + super.init(); + this.memberTreeRoot.set(DefaultLevel1Root); + this.projectInfoTreeRoot.set(DefaultLevel1Root); + this.payeeTreeRoot.set(DefaultLevel1Root); + this.lastRolledUpActionState.set(Reducer.initialActionState); + } + + @method createProject(input: CreateProjectInput) { + this.reducer.dispatch( + new ProjectAction({ + projectId: Field(-1), + members: input.members, + ipfsHash: input.ipfsHash, + payeeAccount: input.payeeAccount, + }) + ); + } + + @method updateProjectInfo(input: UpdateProjectInput) { + // check the right projectId + let projectId = input.memberLevel1Witness.calculateIndex(); + projectId.assertEquals(input.projectId); + + // check only project created can be updated + projectId.assertLessThan(this.nextProjectId.getAndRequireEquals()); + + // check the right owner index + let memberIndex = input.memberLevel2Witness.calculateIndex(); + memberIndex.assertEquals(Field(0)); + // check the same on root + let memberLevel2Root = input.memberLevel2Witness.calculateRoot( + Poseidon.hash(PublicKey.toFields(this.sender)) + ); + let memberLevel1Root = + input.memberLevel1Witness.calculateRoot(memberLevel2Root); + memberLevel1Root.assertEquals( + this.memberTreeRoot.getAndRequireEquals() ); - ////// caculate new memberTreeRoot - let newProjectIndex = memberWitness.calculateIndex(); - let preMemberRoot = memberWitness.calculateRoot(Field(0)); - newProjectId.assertEquals(newProjectIndex); - preMemberRoot.assertEquals(preProof.publicOutput.finalMemberTreeRoot); - - let tree = EMPTY_LEVEL_2_TREE(); - for (let i = 0; i < PROJECT_MEMBER_MAX_SIZE; i++) { - let value = Provable.if( - Field(i).greaterThanOrEqual(newAction.members.length), - Field(0), - MemberArray.hash(newAction.members.get(Field(i))) - ); - tree.setLeaf(BigInt(i), value); - } - - // update new member tree - let newMemberTreeRoot = memberWitness.calculateRoot(tree.getRoot()); - - ////// caculate new projectInfoTreeRoot - let preProjectInfoRoot = projectInfoWitess.calculateRoot(Field(0)); - let projectInfoIndex = projectInfoWitess.calculateIndex(); - projectInfoIndex.assertEquals(newProjectId); - preProjectInfoRoot.assertEquals( - preProof.publicOutput.finalProjectInfoTreeRoot + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + // TODO: not really able to do this, check again. If both of them send at the same block + // checking if the request have the same id already exists within the accumulator + let { state: exists } = this.reducer.reduce( + this.reducer.getActions({ + fromActionState: lastRolledUpActionState, + }), + Bool, + (state: Bool, action: ProjectAction) => { + return action.projectId.equals(projectId).or(state); + }, + // initial state + { state: Bool(false), actionState: lastRolledUpActionState } ); - // update project info tree with hash ipfs hash - let newProjectInfoTreeRoot = projectInfoWitess.calculateRoot( - InfoStorage.calculateLeaf(newAction.ipfsHash) + // if exists then don't dispatch any more + exists.assertEquals(Bool(false)); + + this.reducer.dispatch( + new ProjectAction({ + projectId: input.projectId, + members: input.members, + ipfsHash: input.ipfsHash, + payeeAccount: input.payeeAccount, + }) + ); + } + + @method rollup(proof: ProjectProof) { + proof.verify(); + let nextProjectId = this.nextProjectId.getAndRequireEquals(); + let memberTreeRoot = this.memberTreeRoot.getAndRequireEquals(); + let projectInfoTreeRoot = + this.projectInfoTreeRoot.getAndRequireEquals(); + let payeeTreeRoot = this.payeeTreeRoot.getAndRequireEquals(); + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + nextProjectId.assertEquals(proof.publicOutput.initialNextProjectId); + memberTreeRoot.assertEquals(proof.publicOutput.initialMemberTreeRoot); + projectInfoTreeRoot.assertEquals( + proof.publicOutput.initialProjectInfoTreeRoot + ); + payeeTreeRoot.assertEquals(proof.publicOutput.initialPayeeTreeRoot); + lastRolledUpActionState.assertEquals( + proof.publicOutput.initialLastRolledUpActionState ); - ////// caculate new addressTreeRoot - let prePayeeTreeRoot = payeeWitness.calculateRoot(Field(0)); - let addressIndex = payeeWitness.calculateIndex(); - addressIndex.assertEquals(newProjectId); - prePayeeTreeRoot.assertEquals(preProof.publicOutput.finalPayeeTreeRoot); + let lastActionState = this.account.actionState.getAndRequireEquals(); + lastActionState.assertEquals( + proof.publicOutput.finalLastRolledUpActionState + ); - // update project info tree with hash ipfs hash - let newPayeeTreeRoot = payeeWitness.calculateRoot( - AddressStorage.calculateLeaf(newAction.payeeAccount) + // update on-chain state + this.nextProjectId.set(proof.publicOutput.finalNextProjectId); + this.memberTreeRoot.set(proof.publicOutput.finalMemberTreeRoot); + this.projectInfoTreeRoot.set( + proof.publicOutput.finalProjectInfoTreeRoot + ); + this.payeeTreeRoot.set(proof.publicOutput.finalPayeeTreeRoot); + this.lastRolledUpActionState.set( + proof.publicOutput.finalLastRolledUpActionState ); - return new CreateProjectProofOutput({ - initialNextProjectId: preProof.publicOutput.initialNextProjectId, - initialMemberTreeRoot: preProof.publicOutput.initialMemberTreeRoot, - initialProjectInfoTreeRoot: - preProof.publicOutput.initialProjectInfoTreeRoot, - initialPayeeTreeRoot: preProof.publicOutput.initialPayeeTreeRoot, - initialLastRolledUpActionState: - preProof.publicOutput.initialLastRolledUpActionState, - finalNextProjectId: nextProjectId, - finalMemberTreeRoot: newMemberTreeRoot, - finalProjectInfoTreeRoot: newProjectInfoTreeRoot, - finalPayeeTreeRoot: newPayeeTreeRoot, - finalLastRolledUpActionState: updateOutOfSnark( - preProof.publicOutput.finalLastRolledUpActionState, - [ProjectAction.toFields(newAction)] - ), - }); - }, - }, - }, -}); + this.emitEvent( + EventEnum.PROJECT_CREATED, + proof.publicOutput.finalNextProjectId.sub(Field(1)) + ); + } -export class ProjectProof extends ZkProgram.Proof(CreateProject) {} + @method checkProjectOwner(input: CheckProjectOwerInput): Bool { + let isOwner = Bool(true); -export enum EventEnum { - PROJECT_CREATED = 'project-created', -} + // check the right projectId + let projectId = input.memberLevel1Witness.calculateIndex(); + isOwner = projectId.equals(input.projectId).and(isOwner); -export class ProjectContract extends SmartContract { - @state(Field) nextProjectId = State(); - @state(Field) memberTreeRoot = State(); - @state(Field) projectInfoTreeRoot = State(); - @state(Field) payeeTreeRoot = State(); - @state(Field) lastRolledUpActionState = State(); - - reducer = Reducer({ actionType: ProjectAction }); - - events = { - [EventEnum.PROJECT_CREATED]: Field, - }; - - init() { - super.init(); - this.memberTreeRoot.set(DefaultLevel1Root); - this.projectInfoTreeRoot.set(DefaultLevel1Root); - this.payeeTreeRoot.set(DefaultLevel1Root); - this.lastRolledUpActionState.set(Reducer.initialActionState); - } - - @method createProject(input: CreateProjectInput) { - this.reducer.dispatch( - new ProjectAction({ - projectId: Field(-1), - members: input.members, - ipfsHash: input.ipfsHash, - payeeAccount: input.payeeAccount, - }) - ); - } - - @method updateProjectInfo(input: UpdateProjectInput) { - // check the right projectId - let projectId = input.memberLevel1Witness.calculateIndex(); - projectId.assertEquals(input.projectId); - - // check only project created can be updated - projectId.assertLessThan(this.nextProjectId.getAndRequireEquals()); - - // check the right owner index - let memberIndex = input.memberLevel2Witness.calculateIndex(); - memberIndex.assertEquals(Field(0)); - // check the same on root - let memberLevel2Root = input.memberLevel2Witness.calculateRoot( - Poseidon.hash(PublicKey.toFields(this.sender)) - ); - let memberLevel1Root = - input.memberLevel1Witness.calculateRoot(memberLevel2Root); - memberLevel1Root.assertEquals(this.memberTreeRoot.getAndRequireEquals()); - - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - // TODO: not really able to do this, check again. If both of them send at the same block - // checking if the request have the same id already exists within the accumulator - let { state: exists } = this.reducer.reduce( - this.reducer.getActions({ - fromActionState: lastRolledUpActionState, - }), - Bool, - (state: Bool, action: ProjectAction) => { - return action.projectId.equals(projectId).or(state); - }, - // initial state - { state: Bool(false), actionState: lastRolledUpActionState } - ); - - // if exists then don't dispatch any more - exists.assertEquals(Bool(false)); - - this.reducer.dispatch( - new ProjectAction({ - projectId: input.projectId, - members: input.members, - ipfsHash: input.ipfsHash, - payeeAccount: input.payeeAccount, - }) - ); - } - - @method rollup(proof: ProjectProof) { - proof.verify(); - let nextProjectId = this.nextProjectId.getAndRequireEquals(); - let memberTreeRoot = this.memberTreeRoot.getAndRequireEquals(); - let projectInfoTreeRoot = this.projectInfoTreeRoot.getAndRequireEquals(); - let payeeTreeRoot = this.payeeTreeRoot.getAndRequireEquals(); - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - nextProjectId.assertEquals(proof.publicOutput.initialNextProjectId); - memberTreeRoot.assertEquals(proof.publicOutput.initialMemberTreeRoot); - projectInfoTreeRoot.assertEquals( - proof.publicOutput.initialProjectInfoTreeRoot - ); - payeeTreeRoot.assertEquals(proof.publicOutput.initialPayeeTreeRoot); - lastRolledUpActionState.assertEquals( - proof.publicOutput.initialLastRolledUpActionState - ); - - let lastActionState = this.account.actionState.getAndRequireEquals(); - lastActionState.assertEquals( - proof.publicOutput.finalLastRolledUpActionState - ); - - // update on-chain state - this.nextProjectId.set(proof.publicOutput.finalNextProjectId); - this.memberTreeRoot.set(proof.publicOutput.finalMemberTreeRoot); - this.projectInfoTreeRoot.set(proof.publicOutput.finalProjectInfoTreeRoot); - this.payeeTreeRoot.set(proof.publicOutput.finalPayeeTreeRoot); - this.lastRolledUpActionState.set( - proof.publicOutput.finalLastRolledUpActionState - ); - - this.emitEvent( - EventEnum.PROJECT_CREATED, - proof.publicOutput.finalNextProjectId.sub(Field(1)) - ); - } - - @method checkProjectOwner(input: CheckProjectOwerInput): Bool { - let isOwner = Bool(true); - - // check the right projectId - let projectId = input.memberLevel1Witness.calculateIndex(); - isOwner = projectId.equals(input.projectId).and(isOwner); - - // check the right owner index, is = 0 - let memberIndex = input.memberLevel2Witness.calculateIndex(); - isOwner = memberIndex.equals(Field(0)).and(isOwner); - // check the same on root - let memberLevel2Root = input.memberLevel2Witness.calculateRoot( - Poseidon.hash(PublicKey.toFields(input.owner)) - ); - let memberLevel1Root = - input.memberLevel1Witness.calculateRoot(memberLevel2Root); - isOwner = memberLevel1Root - .equals(this.memberTreeRoot.getAndRequireEquals()) - .and(isOwner); - - return isOwner; - } + // check the right owner index, is = 0 + let memberIndex = input.memberLevel2Witness.calculateIndex(); + isOwner = memberIndex.equals(Field(0)).and(isOwner); + // check the same on root + let memberLevel2Root = input.memberLevel2Witness.calculateRoot( + Poseidon.hash(PublicKey.toFields(input.owner)) + ); + let memberLevel1Root = + input.memberLevel1Witness.calculateRoot(memberLevel2Root); + isOwner = memberLevel1Root + .equals(this.memberTreeRoot.getAndRequireEquals()) + .and(isOwner); + + return isOwner; + } } diff --git a/src/contracts/ProjectStorage.ts b/src/contracts/ProjectStorage.ts index de4a61c..f6b3885 100644 --- a/src/contracts/ProjectStorage.ts +++ b/src/contracts/ProjectStorage.ts @@ -1,19 +1,18 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { - Field, - MerkleTree, - MerkleWitness, - Poseidon, - PublicKey, - Struct, + Field, + MerkleTree, + MerkleWitness, + Poseidon, + PublicKey, + Struct, } from 'o1js'; import { PROJECT_MEMBER_MAX_SIZE, INSTANCE_LIMITS } from '../constants.js'; import { IPFSHash, PublicKeyDynamicArray } from '@auxo-dev/auxo-libs'; export const LEVEL_1_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.PROJECT)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.PROJECT)) + 1; export const LEVEL_2_TREE_HEIGHT = - Math.ceil(Math.log2(PROJECT_MEMBER_MAX_SIZE)) + 1; + Math.ceil(Math.log2(PROJECT_MEMBER_MAX_SIZE)) + 1; export class Level1MT extends MerkleTree {} export class Level1Witness extends MerkleWitness(LEVEL_1_TREE_HEIGHT) {} @@ -24,171 +23,270 @@ export const EMPTY_LEVEL_1_TREE = () => new Level1MT(LEVEL_1_TREE_HEIGHT); export const EMPTY_LEVEL_2_TREE = () => new Level2MT(LEVEL_2_TREE_HEIGHT); export class FullMTWitness extends Struct({ - level1: Level1Witness, - level2: Level2Witness, + level1: Level1Witness, + level2: Level2Witness, }) {} // Storage -export abstract class ProjectStorage { - level1: Level1MT; - level2s: { [key: string]: Level2MT }; - - constructor( - level1?: Level1MT, - level2s?: { index: Field; level2: Level2MT }[] - ) { - this.level1 = level1 || EMPTY_LEVEL_1_TREE(); - this.level2s = {}; - if (level2s) { - for (let i = 0; i < level2s.length; i++) { - this.level2s[level2s[i].index.toString()] = level2s[i].level2; - } - } - } - - abstract calculateLeaf(args: any): Field; - abstract calculateLevel1Index(args: any): Field; - calculateLevel2Index?(args: any): Field; - - getLevel1Witness(level1Index: Field): Level1Witness { - return new Level1Witness(this.level1.getWitness(level1Index.toBigInt())); - } - - getLevel2Witness(level1Index: Field, level2Index: Field): Level2Witness { - let level2 = this.level2s[level1Index.toString()]; - if (level2 === undefined) - throw new Error('Level 2 MT does not exist at this index'); - return new Level2Witness(level2.getWitness(level2Index.toBigInt())); - } - - getWitness( - level1Index: Field, - level2Index?: Field - ): Level1Witness | FullMTWitness { - if (level2Index) { - return new FullMTWitness({ - level1: this.getLevel1Witness(level1Index), - level2: this.getLevel2Witness(level1Index, level2Index), - }); - } else { - return this.getLevel1Witness(level1Index); - } - } - - updateInternal(level1Index: Field, level2: Level2MT) { - Object.assign(this.level2s, { - [level1Index.toString()]: level2, - }); - this.level1.setLeaf(level1Index.toBigInt(), level2.getRoot()); - } - - updateLeaf(leaf: Field, level1Index: Field, level2Index?: Field): void { - if (level2Index) { - if (Object.keys(this.level2s).length == 0) - throw new Error('This storage does support level 2 MT'); - - let level2 = this.level2s[level1Index.toString()]; - if (level2 === undefined) level2 = EMPTY_LEVEL_2_TREE(); - - level2.setLeaf(level2Index.toBigInt(), leaf); - this.updateInternal(level1Index, level2); - } else this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class ProjectStorage { + private _level1: Level1MT; + private _level2s: { [key: string]: Level2MT }; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + level2Index?: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_TREE(); + this._level2s = {}; + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + { + level1Index: leafs[i].level1Index, + level2Index: leafs[i].level2Index, + }, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + { + level1Index: leafs[i].level1Index, + level2Index: leafs[i].level2Index, + }, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + calculateLevel2Index?(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1Witness { + return new Level1Witness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getLevel2Witness(level1Index: Field, level2Index: Field): Level2Witness { + let level2 = this._level2s[level1Index.toString()]; + if (level2 === undefined) + throw new Error('Level 2 MT does not exist at this index'); + return new Level2Witness(level2.getWitness(level2Index.toBigInt())); + } + + getWitness( + level1Index: Field, + level2Index?: Field + ): Level1Witness | FullMTWitness { + if (level2Index) { + return new FullMTWitness({ + level1: this.getLevel1Witness(level1Index), + level2: this.getLevel2Witness(level1Index, level2Index), + }); + } else { + return this.getLevel1Witness(level1Index); + } + } + + updateInternal(level1Index: Field, level2: Level2MT) { + Object.assign(this._level2s, { + [level1Index.toString()]: level2, + }); + this._level1.setLeaf(level1Index.toBigInt(), level2.getRoot()); + } + + updateLeaf( + { + level1Index, + level2Index, + }: { level1Index: Field; level2Index?: Field }, + leaf: Field + ): void { + let leafId = level1Index.toString(); + if (level2Index) { + leafId += '-' + level2Index.toString(); + let level2 = this._level2s[level1Index.toString()]; + if (level2 === undefined) level2 = EMPTY_LEVEL_2_TREE(); + + level2.setLeaf(level2Index.toBigInt(), leaf); + this.updateInternal(level1Index, level2); + } else this._level1.setLeaf(level1Index.toBigInt(), leaf); + + this._leafs[leafId] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf( + { + level1Index, + level2Index, + }: { level1Index: Field; level2Index?: Field }, + rawLeaf: RawLeaf + ): void { + let leafId = level1Index.toString(); + let leaf = this.calculateLeaf(rawLeaf); + if (level2Index) { + leafId += '-' + level2Index.toString(); + let level2 = this._level2s[level1Index.toString()]; + if (level2 === undefined) level2 = EMPTY_LEVEL_2_TREE(); + + level2.setLeaf(level2Index.toBigInt(), leaf); + this.updateInternal(level1Index, level2); + } else this._level1.setLeaf(level1Index.toBigInt(), leaf); + + this._leafs[leafId] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class MemberStorage extends ProjectStorage { - level1: Level1MT; - level2s: { [key: string]: Level2MT }; +export type MemberLeaf = PublicKey; - constructor( - level1?: Level1MT, - level2s?: { index: Field; level2: Level2MT }[] - ) { - super(level1, level2s); - } +export class MemberStorage extends ProjectStorage { + static calculateLeaf(publicKey: MemberLeaf): Field { + return Poseidon.hash(publicKey.toFields()); + } - calculateLeaf(publicKey: PublicKey): Field { - return MemberStorage.calculateLeaf(publicKey); - } + calculateLeaf(publicKey: MemberLeaf): Field { + return MemberStorage.calculateLeaf(publicKey); + } - static calculateLeaf(publicKey: PublicKey): Field { - return Poseidon.hash(publicKey.toFields()); - } + static calculateLevel1Index(projectId: Field): Field { + return projectId; + } - calculateLevel1Index(projectId: Field): Field { - return projectId; - } + calculateLevel1Index(projectId: Field): Field { + return MemberStorage.calculateLevel1Index(projectId); + } - calculateLevel2Index(memberId: Field): Field { - return memberId; - } + static calculateLevel2Index(memberId: Field): Field { + return memberId; + } - getWitness(level1Index: Field, level2Index: Field): FullMTWitness { - return super.getWitness(level1Index, level2Index) as FullMTWitness; - } + calculateLevel2Index(memberId: Field): Field { + return MemberStorage.calculateLevel2Index(memberId); + } + + getWitness(level1Index: Field, level2Index: Field): FullMTWitness { + return super.getWitness(level1Index, level2Index) as FullMTWitness; + } - updateLeaf(leaf: Field, level1Index: Field, level2Index?: Field): void { - super.updateLeaf(leaf, level1Index, level2Index ?? undefined); - } + updateLeaf( + { + level1Index, + level2Index, + }: { level1Index: Field; level2Index: Field }, + leaf: Field + ): void { + super.updateLeaf({ level1Index, level2Index }, leaf); + } + + updateRawLeaf( + { + level1Index, + level2Index, + }: { level1Index: Field; level2Index: Field }, + rawLeaf: MemberLeaf + ): void { + super.updateRawLeaf({ level1Index, level2Index }, rawLeaf); + } } -export class InfoStorage extends ProjectStorage { - level1: Level1MT; +export type InfoLeaf = IPFSHash; - constructor(level1?: Level1MT) { - super(level1); - } +export class InfoStorage extends ProjectStorage { + static calculateLeaf(ipfsHash: InfoLeaf): Field { + return Poseidon.hash(ipfsHash.toFields()); + } - calculateLeaf(ipfsHash: IPFSHash): Field { - return InfoStorage.calculateLeaf(ipfsHash); - } + calculateLeaf(ipfsHash: InfoLeaf): Field { + return InfoStorage.calculateLeaf(ipfsHash); + } - static calculateLeaf(ipfsHash: IPFSHash): Field { - return Poseidon.hash(ipfsHash.toFields()); - } + static calculateLevel1Index(projectId: Field): Field { + return projectId; + } + + calculateLevel1Index(projectId: Field): Field { + return InfoStorage.calculateLevel1Index(projectId); + } - calculateLevel1Index(projectId: Field): Field { - return projectId; - } + getWitness(level1Index: Field): Level1Witness { + return super.getWitness(level1Index) as Level1Witness; + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } + updateLeaf({ level1Index }: { level1Index: Field }, leaf: Field): void { + super.updateLeaf({ level1Index }, leaf); + } - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + updateRawLeaf( + { level1Index }: { level1Index: Field }, + rawLeaf: InfoLeaf + ): void { + super.updateRawLeaf({ level1Index }, rawLeaf); + } } -export class AddressStorage extends ProjectStorage { - level1: Level1MT; +export type AddressLeaf = PublicKey; - constructor(level1?: Level1MT) { - super(level1); - } +export class AddressStorage extends ProjectStorage { + static calculateLeaf(address: AddressLeaf): Field { + return Poseidon.hash(address.toFields()); + } - calculateLeaf(address: PublicKey): Field { - return AddressStorage.calculateLeaf(address); - } + calculateLeaf(address: AddressLeaf): Field { + return AddressStorage.calculateLeaf(address); + } - static calculateLeaf(address: PublicKey): Field { - return Poseidon.hash(address.toFields()); - } + static calculateLevel1Index(projectId: Field): Field { + return projectId; + } - calculateLevel1Index(projectId: Field): Field { - return projectId; - } + calculateLevel1Index(projectId: Field): Field { + return AddressStorage.calculateLevel1Index(projectId); + } - getWitness(level1Index: Field): Level1Witness { - return super.getWitness(level1Index) as Level1Witness; - } + getWitness(level1Index: Field): Level1Witness { + return super.getWitness(level1Index) as Level1Witness; + } - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } + updateLeaf({ level1Index }: { level1Index: Field }, leaf: Field): void { + super.updateLeaf({ level1Index }, leaf); + } + + updateRawLeaf( + { level1Index }: { level1Index: Field }, + rawLeaf: AddressLeaf + ): void { + super.updateRawLeaf({ level1Index }, rawLeaf); + } } // Type export class MemberArray extends PublicKeyDynamicArray( - PROJECT_MEMBER_MAX_SIZE + PROJECT_MEMBER_MAX_SIZE ) {} diff --git a/src/contracts/SharedStorage.ts b/src/contracts/SharedStorage.ts index 7ae4fc8..0c8e00d 100644 --- a/src/contracts/SharedStorage.ts +++ b/src/contracts/SharedStorage.ts @@ -1,12 +1,12 @@ import { - Field, - MerkleMap, - MerkleMapWitness, - MerkleTree, - MerkleWitness, - Poseidon, - PublicKey, - Struct, + Field, + MerkleMap, + MerkleMapWitness, + MerkleTree, + MerkleWitness, + Poseidon, + PublicKey, + Struct, } from 'o1js'; import { ADDRESS_MAX_SIZE } from '../constants.js'; @@ -18,81 +18,156 @@ export class ReduceWitness extends MerkleMapWitness {} export const EMPTY_REDUCE_MT = () => new MerkleMap(); export class ZkAppRef extends Struct({ - address: PublicKey, - witness: AddressWitness, + address: PublicKey, + witness: AddressWitness, }) {} export class AddressStorage { - addresses: AddressMT; - - constructor(addresses?: AddressMT) { - this.addresses = addresses || EMPTY_ADDRESS_MT(); - } - - static calculateLeaf(address: PublicKey): Field { - return Poseidon.hash(address.toFields()); - } - - calculateLeaf(address: PublicKey): Field { - return AddressStorage.calculateLeaf(address); - } - - static calculateIndex(index: Field | number): Field { - return Field(index); - } - - calculateIndex(index: Field | number): Field { - return AddressStorage.calculateIndex(index); - } - - getWitness(index: Field): AddressWitness { - return new AddressWitness(this.addresses.getWitness(index.toBigInt())); - } + private _addressMap: AddressMT; + private _addresses: { + [key: string]: { raw: PublicKey | undefined; leaf: Field }; + }; + + constructor(addresses?: { index: Field | number; address: PublicKey }[]) { + this._addressMap = EMPTY_ADDRESS_MT(); + this._addresses = {}; + if (addresses) { + for (let i = 0; i < addresses.length; i++) { + this.updateAddress( + AddressStorage.calculateIndex(addresses[i].index), + addresses[i].address + ); + } + } + } + + get root(): Field { + return this._addressMap.getRoot(); + } + + get addresses(): { + [key: string]: { raw: PublicKey | undefined; leaf: Field }; + } { + return this._addresses; + } + + static calculateLeaf(address: PublicKey): Field { + return Poseidon.hash(address.toFields()); + } + + calculateLeaf(address: PublicKey): Field { + return AddressStorage.calculateLeaf(address); + } + + static calculateIndex(index: Field | number): Field { + return Field(index); + } + + calculateIndex(index: Field | number): Field { + return AddressStorage.calculateIndex(index); + } + + getWitness(index: Field): AddressWitness { + return new AddressWitness( + this._addressMap.getWitness(index.toBigInt()) + ); + } + + getAddresses(): (PublicKey | undefined)[] { + return Object.values(this._addressMap); + } + + updateLeaf(index: Field, leaf: Field): void { + this._addressMap.setLeaf(index.toBigInt(), leaf); + this._addresses[index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateAddress(index: Field, address: PublicKey) { + let leaf = this.calculateLeaf(address); + this._addressMap.setLeaf(index.toBigInt(), leaf); + this._addresses[index.toString()] = { + raw: address, + leaf: leaf, + }; + } + + getZkAppRef(index: Field | number, address: PublicKey) { + return new ZkAppRef({ + address: address, + witness: this.getWitness(this.calculateIndex(index)), + }); + } } export function getZkAppRef( - map: AddressMT, - index: Field | number, - address: PublicKey + map: AddressMT, + index: Field | number, + address: PublicKey ) { - return new ZkAppRef({ - address: address, - witness: new AddressWitness( - map.getWitness(new AddressStorage().calculateIndex(index).toBigInt()) - ), - }); + return new ZkAppRef({ + address: address, + witness: new AddressWitness( + map.getWitness(AddressStorage.calculateIndex(index).toBigInt()) + ), + }); } export const enum ActionStatus { - NOT_EXISTED, - REDUCED, - ROLL_UPED, + NOT_EXISTED, + REDUCED, + ROLL_UPED, } export class ReduceStorage { - actions: MerkleMap; - - constructor(actions?: MerkleMap) { - this.actions = actions || EMPTY_REDUCE_MT(); - } - - static calculateLeaf(status: ActionStatus): Field { - return Field(status); - } - - calculateLeaf(status: ActionStatus): Field { - return ReduceStorage.calculateLeaf(status); - } - - calculateIndex(actionState: Field): Field { - return actionState; - } - - getWitness(index: Field): MerkleMapWitness { - return this.actions.getWitness(index); - } - - updateLeaf(index: Field, leaf: Field): void { - this.actions.set(index, leaf); - } + private _actionMap: MerkleMap; + private _actions: { [key: string]: Field }; + + constructor(actions?: { actionState: Field; status: ActionStatus }[]) { + this._actionMap = EMPTY_REDUCE_MT(); + this._actions = {}; + if (actions) { + for (let i = 0; i < actions.length; i++) { + this.updateLeaf( + actions[i].actionState, + ReduceStorage.calculateLeaf(actions[i].status) + ); + } + } + } + + get root(): Field { + return this._actionMap.getRoot(); + } + + get actions(): { [key: string]: Field } { + return this._actions; + } + + static calculateLeaf(status: ActionStatus): Field { + return Field(status); + } + + calculateLeaf(status: ActionStatus): Field { + return ReduceStorage.calculateLeaf(status); + } + + static calculateIndex(actionState: Field): Field { + return actionState; + } + + calculateIndex(actionState: Field): Field { + return ReduceStorage.calculateIndex(actionState); + } + + getWitness(index: Field): MerkleMapWitness { + return this._actionMap.getWitness(index); + } + + updateLeaf(index: Field, leaf: Field): void { + this._actionMap.set(index, leaf); + this._actions[index.toString()] = leaf; + } } diff --git a/src/contracts/Treasury.ts b/src/contracts/Treasury.ts index dd5f197..ccb7bbc 100644 --- a/src/contracts/Treasury.ts +++ b/src/contracts/Treasury.ts @@ -1,22 +1,22 @@ import { - Field, - SmartContract, - state, - State, - method, - Reducer, - Struct, - SelfProof, - Poseidon, - Provable, - ZkProgram, - PublicKey, - Void, - Bool, - MerkleMapWitness, - Group, - Scalar, - UInt64, + Field, + SmartContract, + state, + State, + method, + Reducer, + Struct, + SelfProof, + Poseidon, + Provable, + ZkProgram, + PublicKey, + Void, + Bool, + MerkleMapWitness, + Group, + Scalar, + UInt64, } from 'o1js'; import { updateOutOfSnark } from '../libs/utils.js'; @@ -25,9 +25,9 @@ import { FieldDynamicArray } from '@auxo-dev/auxo-libs'; import { ZkAppRef } from './SharedStorage.js'; import { - EMPTY_LEVEL_1_TREE, - Level1CWitness, - ClaimedStorage, + EMPTY_LEVEL_1_TREE, + Level1CWitness, + ClaimedStorage, } from './TreasuryStorage.js'; import { ZkApp } from '@auxo-dev/dkg'; @@ -39,278 +39,282 @@ import { Level1CWitness as indexWitness } from './ParticipationStorage.js'; const DefaultLevel1Root = EMPTY_LEVEL_1_TREE().getRoot(); export class InvestVector extends FieldDynamicArray( - INSTANCE_LIMITS.PARTICIPATION + INSTANCE_LIMITS.PARTICIPATION ) {} export class TreasuryAction extends Struct({ - campaignId: Field, - projectId: Field, + campaignId: Field, + projectId: Field, }) { - static fromFields(fields: Field[]): TreasuryAction { - return super.fromFields(fields) as TreasuryAction; - } + static fromFields(fields: Field[]): TreasuryAction { + return super.fromFields(fields) as TreasuryAction; + } - id(): Field { - return Poseidon.hash([this.campaignId, this.projectId]); - } + id(): Field { + return Poseidon.hash([this.campaignId, this.projectId]); + } } export class ClaimFundInput extends Struct({ - campaignId: Field, - projectId: Field, - requestId: Field, // TODO: Funding check requestId - payeeAddress: PublicKey, // TODO: Project check address - M: ZkApp.Request.RequestVector, - D: ZkApp.Request.RequestVector, - DWitness: MerkleMapWitness, - investVector: InvestVector, - participationIndex: Field, - indexWitness: indexWitness, - claimedIndex: Level1CWitness, - participationRef: ZkAppRef, + campaignId: Field, + projectId: Field, + requestId: Field, // TODO: Funding check requestId + payeeAddress: PublicKey, // TODO: Project check address + M: ZkApp.Request.RequestVector, + D: ZkApp.Request.RequestVector, + DWitness: MerkleMapWitness, + investVector: InvestVector, + participationIndex: Field, + indexWitness: indexWitness, + claimedIndex: Level1CWitness, + participationRef: ZkAppRef, }) { - static fromFields(fields: Field[]): ClaimFundInput { - return super.fromFields(fields) as ClaimFundInput; - } + static fromFields(fields: Field[]): ClaimFundInput { + return super.fromFields(fields) as ClaimFundInput; + } } export class CheckIfNotClaimedInput extends Struct({ - campaignId: Field, - projectId: Field, - claimedIndex: Level1CWitness, + campaignId: Field, + projectId: Field, + claimedIndex: Level1CWitness, }) { - static fromFields(fields: Field[]): CheckIfNotClaimedInput { - return super.fromFields(fields) as CheckIfNotClaimedInput; - } + static fromFields(fields: Field[]): CheckIfNotClaimedInput { + return super.fromFields(fields) as CheckIfNotClaimedInput; + } } export class ClaimFundProofOutput extends Struct({ - initialClaimedTreeRoot: Field, - initiallastRolledUpActionState: Field, - finalClaimedTreeRoot: Field, - finalLastRolledUpActionState: Field, + initialClaimedTreeRoot: Field, + initiallastRolledUpActionState: Field, + finalClaimedTreeRoot: Field, + finalLastRolledUpActionState: Field, }) { - hash(): Field { - return Poseidon.hash(ClaimFundProofOutput.toFields(this)); - } + hash(): Field { + return Poseidon.hash(ClaimFundProofOutput.toFields(this)); + } } export const ClaimFund = ZkProgram({ - name: 'claim-fund', - publicOutput: ClaimFundProofOutput, - methods: { - firstStep: { - privateInputs: [Field, Field], - method( - initialClaimedTreeRoot, - initiallastRolledUpActionState - ): ClaimFundProofOutput { - return new ClaimFundProofOutput({ - initialClaimedTreeRoot, - initiallastRolledUpActionState, - finalClaimedTreeRoot: initialClaimedTreeRoot, - finalLastRolledUpActionState: initiallastRolledUpActionState, - }); - }, + name: 'claim-fund', + publicOutput: ClaimFundProofOutput, + methods: { + firstStep: { + privateInputs: [Field, Field], + method( + initialClaimedTreeRoot, + initiallastRolledUpActionState + ): ClaimFundProofOutput { + return new ClaimFundProofOutput({ + initialClaimedTreeRoot, + initiallastRolledUpActionState, + finalClaimedTreeRoot: initialClaimedTreeRoot, + finalLastRolledUpActionState: + initiallastRolledUpActionState, + }); + }, + }, + createTreasury: { + privateInputs: [ + SelfProof, + TreasuryAction, + Level1CWitness, + ], + method( + preProof: SelfProof, + newAction: TreasuryAction, + claimedIndex: Level1CWitness + ): ClaimFundProofOutput { + preProof.verify(); + + let index = ClaimedStorage.calculateLevel1Index({ + campaignId: newAction.campaignId, + projectId: newAction.projectId, + }); + index.assertEquals(claimedIndex.calculateIndex()); + + let curClaimedTreeRoot = claimedIndex.calculateRoot(Field(0)); + curClaimedTreeRoot.assertEquals( + preProof.publicOutput.finalClaimedTreeRoot + ); + + let newClaimedTreeRoot = claimedIndex.calculateRoot( + ClaimedStorage.calculateLeaf(Bool(true)) + ); + + return new ClaimFundProofOutput({ + initialClaimedTreeRoot: + preProof.publicOutput.initialClaimedTreeRoot, + initiallastRolledUpActionState: + preProof.publicOutput.initiallastRolledUpActionState, + finalClaimedTreeRoot: newClaimedTreeRoot, + finalLastRolledUpActionState: updateOutOfSnark( + preProof.publicOutput.finalLastRolledUpActionState, + [TreasuryAction.toFields(newAction)] + ), + }); + }, + }, }, - createTreasury: { - privateInputs: [ - SelfProof, - TreasuryAction, - Level1CWitness, - ], - method( - preProof: SelfProof, - newAction: TreasuryAction, - claimedIndex: Level1CWitness - ): ClaimFundProofOutput { - preProof.verify(); +}); - let index = ClaimedStorage.calculateLevel1Index({ - campaignId: newAction.campaignId, - projectId: newAction.projectId, +class TreasuryProof extends ZkProgram.Proof(ClaimFund) {} + +export enum EventEnum { + ACTIONS_REDUCED = 'actions-reduced', +} + +export class TreasuryContract extends SmartContract { + // store claimed status + @state(Field) claimedTreeRoot = State(); + // MT of other zkApp address + @state(Field) zkApps = State(); + @state(Field) lastRolledUpActionState = State(); + + reducer = Reducer({ actionType: TreasuryAction }); + + events = { + [EventEnum.ACTIONS_REDUCED]: Field, + }; + + init() { + super.init(); + this.claimedTreeRoot.set(DefaultLevel1Root); + this.lastRolledUpActionState.set(Reducer.initialActionState); + } + + @method claimFund(input: ClaimFundInput) { + // TODO: check campaign config + // TODO: check D value in contract Request + + let action = new TreasuryAction({ + campaignId: input.campaignId, + projectId: input.projectId, }); - index.assertEquals(claimedIndex.calculateIndex()); - let curClaimedTreeRoot = claimedIndex.calculateRoot(Field(0)); - curClaimedTreeRoot.assertEquals( - preProof.publicOutput.finalClaimedTreeRoot + let id = action.id(); + + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + // TODO: not really able to do this, check again. If both of them send at the same block + // checking if the request have the same id already exists within the accumulator + let { state: exists } = this.reducer.reduce( + this.reducer.getActions({ + fromActionState: lastRolledUpActionState, + }), + Bool, + (state: Bool, action: TreasuryAction) => { + return action.id().equals(id).or(state); + }, + // initial state + { state: Bool(false), actionState: lastRolledUpActionState } ); - let newClaimedTreeRoot = claimedIndex.calculateRoot( - ClaimedStorage.calculateLeaf(Bool(true)) + // if exists then don't dispatch any more + exists.assertEquals(Bool(false)); + + for (let i = 0; i < INSTANCE_LIMITS.PARTICIPATION; i++) { + let sumMsubSumD = input.M.get(Field(i)).sub(input.D.get(Field(i))); + let point = Provable.witness(Group, () => { + return Group.generator.scale( + Scalar.from(input.investVector.get(Field(i)).toBigInt()) + ); + }); + point.x.assertEquals(sumMsubSumD.x); + point.y.assertEquals(sumMsubSumD.y); + } + + let zkApps = this.zkApps.getAndRequireEquals(); + // Verify zkApp references + zkApps.assertEquals( + input.participationRef.witness.calculateRoot( + Poseidon.hash(input.participationRef.address.toFields()) + ) + ); + Field(ZkAppEnum.PARTICIPATION).assertEquals( + input.participationRef.witness.calculateIndex() ); - return new ClaimFundProofOutput({ - initialClaimedTreeRoot: preProof.publicOutput.initialClaimedTreeRoot, - initiallastRolledUpActionState: - preProof.publicOutput.initiallastRolledUpActionState, - finalClaimedTreeRoot: newClaimedTreeRoot, - finalLastRolledUpActionState: updateOutOfSnark( - preProof.publicOutput.finalLastRolledUpActionState, - [TreasuryAction.toFields(newAction)] - ), - }); - }, - }, - }, -}); + // TODO: check this latter + // const participationContract = new ParticipationContract( + // input.participationRef.address + // ); + + // participationContract + // .checkParticipationIndex( + // new checkParticipationIndexInput({ + // campaignId: input.campaignId, + // projectId: input.projectId, + // participationIndex: input.participationIndex, + // indexWitness: input.indexWitness, + // }) + // ) + // .assertEquals(Bool(true)); + + // check if claimed + this.checkIfNotClaimed( + new CheckIfNotClaimedInput({ + campaignId: input.campaignId, + projectId: input.projectId, + claimedIndex: input.claimedIndex, + }) + ).assertEquals(Bool(true)); + + let claimAmount = input.investVector.get( + input.participationIndex.sub(Field(1)) // since index start from 1 + ); -class TreasuryProof extends ZkProgram.Proof(ClaimFund) {} + // send invest amount + this.send({ to: input.payeeAddress, amount: UInt64.from(claimAmount) }); -export enum EventEnum { - ACTIONS_REDUCED = 'actions-reduced', -} + this.reducer.dispatch(action); + } -export class TreasuryContract extends SmartContract { - // store claimed status - @state(Field) claimedTreeRoot = State(); - // MT of other zkApp address - @state(Field) zkApps = State(); - @state(Field) lastRolledUpActionState = State(); - - reducer = Reducer({ actionType: TreasuryAction }); - - events = { - [EventEnum.ACTIONS_REDUCED]: Field, - }; - - init() { - super.init(); - this.claimedTreeRoot.set(DefaultLevel1Root); - this.lastRolledUpActionState.set(Reducer.initialActionState); - } - - @method claimFund(input: ClaimFundInput) { - // TODO: check campaign config - // TODO: check D value in contract Request - - let action = new TreasuryAction({ - campaignId: input.campaignId, - projectId: input.projectId, - }); - - let id = action.id(); - - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - // TODO: not really able to do this, check again. If both of them send at the same block - // checking if the request have the same id already exists within the accumulator - let { state: exists } = this.reducer.reduce( - this.reducer.getActions({ - fromActionState: lastRolledUpActionState, - }), - Bool, - (state: Bool, action: TreasuryAction) => { - return action.id().equals(id).or(state); - }, - // initial state - { state: Bool(false), actionState: lastRolledUpActionState } - ); - - // if exists then don't dispatch any more - exists.assertEquals(Bool(false)); - - for (let i = 0; i < INSTANCE_LIMITS.PARTICIPATION; i++) { - let sumMsubSumD = input.M.get(Field(i)).sub(input.D.get(Field(i))); - let point = Provable.witness(Group, () => { - return Group.generator.scale( - Scalar.from(input.investVector.get(Field(i)).toBigInt()) + @method rollup(proof: TreasuryProof) { + proof.verify(); + + let claimedTreeRoot = this.claimedTreeRoot.getAndRequireEquals(); + let lastRolledUpActionState = + this.lastRolledUpActionState.getAndRequireEquals(); + + claimedTreeRoot.assertEquals(proof.publicOutput.initialClaimedTreeRoot); + lastRolledUpActionState.assertEquals( + proof.publicOutput.initiallastRolledUpActionState ); - }); - point.x.assertEquals(sumMsubSumD.x); - point.y.assertEquals(sumMsubSumD.y); + + let lastActionState = this.account.actionState.getAndRequireEquals(); + lastActionState.assertEquals( + proof.publicOutput.finalLastRolledUpActionState + ); + + // update on-chain state + this.claimedTreeRoot.set(proof.publicOutput.finalClaimedTreeRoot); + this.lastRolledUpActionState.set( + proof.publicOutput.finalLastRolledUpActionState + ); + + this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); } - let zkApps = this.zkApps.getAndRequireEquals(); - // Verify zkApp references - zkApps.assertEquals( - input.participationRef.witness.calculateRoot( - Poseidon.hash(input.participationRef.address.toFields()) - ) - ); - Field(ZkAppEnum.PARTICIPATION).assertEquals( - input.participationRef.witness.calculateIndex() - ); - - // TODO: check this latter - // const participationContract = new ParticipationContract( - // input.participationRef.address - // ); - - // participationContract - // .checkParticipationIndex( - // new checkParticipationIndexInput({ - // campaignId: input.campaignId, - // projectId: input.projectId, - // participationIndex: input.participationIndex, - // indexWitness: input.indexWitness, - // }) - // ) - // .assertEquals(Bool(true)); - - // check if claimed - this.checkIfNotClaimed( - new CheckIfNotClaimedInput({ - campaignId: input.campaignId, - projectId: input.projectId, - claimedIndex: input.claimedIndex, - }) - ).assertEquals(Bool(true)); - - let claimAmount = input.investVector.get( - input.participationIndex.sub(Field(1)) // since index start from 1 - ); - - // send invest amount - this.send({ to: input.payeeAddress, amount: UInt64.from(claimAmount) }); - - this.reducer.dispatch(action); - } - - @method rollup(proof: TreasuryProof) { - proof.verify(); - - let claimedTreeRoot = this.claimedTreeRoot.getAndRequireEquals(); - let lastRolledUpActionState = - this.lastRolledUpActionState.getAndRequireEquals(); - - claimedTreeRoot.assertEquals(proof.publicOutput.initialClaimedTreeRoot); - lastRolledUpActionState.assertEquals( - proof.publicOutput.initiallastRolledUpActionState - ); - - let lastActionState = this.account.actionState.getAndRequireEquals(); - lastActionState.assertEquals( - proof.publicOutput.finalLastRolledUpActionState - ); - - // update on-chain state - this.claimedTreeRoot.set(proof.publicOutput.finalClaimedTreeRoot); - this.lastRolledUpActionState.set( - proof.publicOutput.finalLastRolledUpActionState - ); - - this.emitEvent(EventEnum.ACTIONS_REDUCED, lastActionState); - } - - @method checkIfNotClaimed(input: CheckIfNotClaimedInput): Bool { - let isNotClaimed = Bool(true); - - let index = ClaimedStorage.calculateLevel1Index({ - campaignId: input.campaignId, - projectId: input.projectId, - }); - isNotClaimed = isNotClaimed.and( - index.equals(input.claimedIndex.calculateIndex()) - ); - - let curClaimedTreeRoot = this.claimedTreeRoot.getAndRequireEquals(); - isNotClaimed = isNotClaimed.and( - curClaimedTreeRoot.equals(input.claimedIndex.calculateRoot(Field(0))) - ); - - return isNotClaimed; - } + @method checkIfNotClaimed(input: CheckIfNotClaimedInput): Bool { + let isNotClaimed = Bool(true); + + let index = ClaimedStorage.calculateLevel1Index({ + campaignId: input.campaignId, + projectId: input.projectId, + }); + isNotClaimed = isNotClaimed.and( + index.equals(input.claimedIndex.calculateIndex()) + ); + + let curClaimedTreeRoot = this.claimedTreeRoot.getAndRequireEquals(); + isNotClaimed = isNotClaimed.and( + curClaimedTreeRoot.equals( + input.claimedIndex.calculateRoot(Field(0)) + ) + ); + + return isNotClaimed; + } } diff --git a/src/contracts/TreasuryStorage.ts b/src/contracts/TreasuryStorage.ts index fa6e2b2..9e4edc6 100644 --- a/src/contracts/TreasuryStorage.ts +++ b/src/contracts/TreasuryStorage.ts @@ -1,86 +1,121 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Bool, Field, MerkleTree, MerkleWitness } from 'o1js'; import { INSTANCE_LIMITS } from '../constants.js'; export const LEVEL_1_COMBINED_TREE_HEIGHT = - Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN * INSTANCE_LIMITS.PROJECT)) + 1; + Math.ceil(Math.log2(INSTANCE_LIMITS.CAMPAIGN * INSTANCE_LIMITS.PROJECT)) + + 1; export class Level1CMT extends MerkleTree {} export class Level1CWitness extends MerkleWitness( - LEVEL_1_COMBINED_TREE_HEIGHT + LEVEL_1_COMBINED_TREE_HEIGHT ) {} export const EMPTY_LEVEL_1_TREE = () => - new Level1CMT(LEVEL_1_COMBINED_TREE_HEIGHT); + new Level1CMT(LEVEL_1_COMBINED_TREE_HEIGHT); // Storage -export abstract class TreasuryStorage { - level1: Level1CMT; - - constructor(level1?: Level1CMT) { - this.level1 = level1 || EMPTY_LEVEL_1_TREE(); - } - - abstract calculateLeaf(args: any): Field; - abstract calculateLevel1Index(args: any): Field; - calculateLevel2Index?(args: any): Field; - - getLevel1Witness(level1Index: Field): Level1CWitness { - return new Level1CWitness(this.level1.getWitness(level1Index.toBigInt())); - } - - getWitness(level1Index: Field): Level1CWitness { - return this.getLevel1Witness(level1Index); - } - - updateLeaf(leaf: Field, level1Index: Field): void { - this.level1.setLeaf(level1Index.toBigInt(), leaf); - } +export abstract class TreasuryCStorage { + private _level1: Level1CMT; + private _leafs: { + [key: string]: { raw: RawLeaf | undefined; leaf: Field }; + }; + + constructor( + leafs?: { + level1Index: Field; + leaf: RawLeaf | Field; + }[] + ) { + this._level1 = EMPTY_LEVEL_1_TREE(); + this._leafs = {}; + if (leafs) { + for (let i = 0; i < leafs.length; i++) { + if (leafs[i].leaf instanceof Field) { + this.updateLeaf( + leafs[i].level1Index, + leafs[i].leaf as Field + ); + } else { + this.updateRawLeaf( + leafs[i].level1Index, + leafs[i].leaf as RawLeaf + ); + } + } + } + } + + get root(): Field { + return this._level1.getRoot(); + } + + get leafs(): { [key: string]: { raw: RawLeaf | undefined; leaf: Field } } { + return this._leafs; + } + + abstract calculateLeaf(rawLeaf: RawLeaf): Field; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + abstract calculateLevel1Index(args: any): Field; + + getLevel1Witness(level1Index: Field): Level1CWitness { + return new Level1CWitness( + this._level1.getWitness(level1Index.toBigInt()) + ); + } + + getWitness(level1Index: Field): Level1CWitness { + return this.getLevel1Witness(level1Index); + } + + updateLeaf(level1Index: Field, leaf: Field): void { + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: undefined, + leaf: leaf, + }; + } + + updateRawLeaf(level1Index: Field, rawLeaf: RawLeaf): void { + let leaf = this.calculateLeaf(rawLeaf); + this._level1.setLeaf(level1Index.toBigInt(), leaf); + this._leafs[level1Index.toString()] = { + raw: rawLeaf, + leaf: leaf, + }; + } } -export class ClaimedStorage extends TreasuryStorage { - level1: Level1CMT; - - constructor(level1?: Level1CMT) { - super(level1); - } - - calculateLeaf(state: Bool): Field { - return ClaimedStorage.calculateLeaf(state); - } - - static calculateLeaf(state: Bool): Field { - return state.toField(); - } - - static calculateLevel1Index({ - campaignId, - projectId, - }: { - campaignId: Field; - projectId: Field; - }): Field { - return campaignId.mul(Field(INSTANCE_LIMITS.PROJECT)).add(projectId); - } - - calculateLevel1Index({ - campaignId, - projectId, - }: { - campaignId: Field; - projectId: Field; - }): Field { - return ClaimedStorage.calculateLevel1Index({ - campaignId: campaignId, - projectId: projectId, - }); - } - - getWitness(level1Index: Field): Level1CWitness { - return super.getWitness(level1Index) as Level1CWitness; - } - - updateLeaf(leaf: Field, level1Index: Field): void { - super.updateLeaf(leaf, level1Index); - } +export type ClaimedLeaf = Bool; + +export class ClaimedStorage extends TreasuryCStorage { + static calculateLeaf(state: ClaimedLeaf): Field { + return state.toField(); + } + + calculateLeaf(state: ClaimedLeaf): Field { + return ClaimedStorage.calculateLeaf(state); + } + + static calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return campaignId.mul(Field(INSTANCE_LIMITS.PROJECT)).add(projectId); + } + + calculateLevel1Index({ + campaignId, + projectId, + }: { + campaignId: Field; + projectId: Field; + }): Field { + return ClaimedStorage.calculateLevel1Index({ + campaignId, + projectId, + }); + } } diff --git a/src/contracts/storages.ts b/src/contracts/storages.ts index d9b9c06..ddafca4 100644 --- a/src/contracts/storages.ts +++ b/src/contracts/storages.ts @@ -5,9 +5,9 @@ import * as FundingStorage from './FundingStorage.js'; import * as TreasuryStorage from './TreasuryStorage.js'; export { - CampaignStorage, - ProjectStorage, - ParticipationStorage, - FundingStorage, - TreasuryStorage, + CampaignStorage, + ProjectStorage, + ParticipationStorage, + FundingStorage, + TreasuryStorage, }; diff --git a/src/libs/utils.ts b/src/libs/utils.ts index 83e81d3..bb71864 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -1,160 +1,160 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { - AccountUpdate, - Cache, - Field, - Mina, - Reducer, - SmartContract, - fetchAccount, + AccountUpdate, + Cache, + Field, + Mina, + Reducer, + SmartContract, + fetchAccount, } from 'o1js'; import { Key } from '../scripts/helper/config.js'; import { Profiler } from '../scripts/helper/profiler.js'; export function updateOutOfSnark(state: Field, action: Field[][]) { - let actionsHash = AccountUpdate.Actions.hash(action); - return AccountUpdate.Actions.updateSequenceState(state, actionsHash); + let actionsHash = AccountUpdate.Actions.hash(action); + return AccountUpdate.Actions.updateSequenceState(state, actionsHash); } const DEFAULT_WAIT_TIME = 7 * 60 * 1000; // 7m export async function wait(time?: number): Promise { - let waitTime = time || DEFAULT_WAIT_TIME; - console.log(`Wait for ${waitTime / 1000}s ...`); - return new Promise((resolve) => setTimeout(resolve, waitTime)); + let waitTime = time || DEFAULT_WAIT_TIME; + console.log(`Wait for ${waitTime / 1000}s ...`); + return new Promise((resolve) => setTimeout(resolve, waitTime)); } export async function compile( - prg: any, - cache?: Cache, - logMemory?: boolean, - profiler?: Profiler + prg: any, + cache?: Cache, + logMemory?: boolean, + profiler?: Profiler ): Promise { - if (logMemory) logMemUsage(); - console.log(`Compiling ${prg.name}...`); - if (profiler) profiler.start(`${prg.name}.compile`); - if (cache) await prg.compile({ cache }); - else await prg.compile(); - if (profiler) profiler.stop(); - console.log('Compiling done!'); + if (logMemory) logMemUsage(); + console.log(`Compiling ${prg.name}...`); + if (profiler) profiler.start(`${prg.name}.compile`); + if (cache) await prg.compile({ cache }); + else await prg.compile(); + if (profiler) profiler.stop(); + console.log('Compiling done!'); } export type ContractList = { - [key: string]: { - name: string; - key: Key; - contract: SmartContract; - actionStates: Field[]; - }; + [key: string]: { + name: string; + key: Key; + contract: SmartContract; + actionStates: Field[]; + }; }; export async function deploy( - ct: { name: string; contract: any; key: Key }, - initArgs: [string, any][], - feePayer: Key, - fee?: number, - nonce?: number + ct: { name: string; contract: any; key: Key }, + initArgs: [string, any][], + feePayer: Key, + fee?: number, + nonce?: number ): Promise { - console.log(`Deploying ${ct.name}...`); - let sender; - if (nonce) { - sender = { sender: feePayer.publicKey, fee: fee, nonce: nonce }; - } else { - sender = { sender: feePayer.publicKey, fee: fee }; - } - let tx = await Mina.transaction(sender, () => { - AccountUpdate.fundNewAccount(feePayer.publicKey, 1); - ct.contract.deploy(); - for (let i = 0; i < initArgs.length; i++) { - ct.contract[initArgs[i][0]].set(initArgs[i][1]); + console.log(`Deploying ${ct.name}...`); + let sender; + if (nonce) { + sender = { sender: feePayer.publicKey, fee: fee, nonce: nonce }; + } else { + sender = { sender: feePayer.publicKey, fee: fee }; } - }); - await tx.sign([feePayer.privateKey, ct.key.privateKey]).send(); - console.log(`${ct.name} deployed!`); + let tx = await Mina.transaction(sender, () => { + AccountUpdate.fundNewAccount(feePayer.publicKey, 1); + ct.contract.deploy(); + for (let i = 0; i < initArgs.length; i++) { + ct.contract[initArgs[i][0]].set(initArgs[i][1]); + } + }); + await tx.sign([feePayer.privateKey, ct.key.privateKey]).send(); + console.log(`${ct.name} deployed!`); } export async function proveAndSend( - tx: Mina.Transaction, - signer: Key[], - contractName: string, - methodName: string, - logMemory?: boolean, - profiler?: Profiler + tx: Mina.Transaction, + signer: Key[], + contractName: string, + methodName: string, + logMemory?: boolean, + profiler?: Profiler ) { - if (logMemory) logMemUsage(); - console.log( - `Generate proof and submit tx for ${contractName}.${methodName}()...` - ); - let retries = 1; // Number of retries - - while (retries > 0) { - try { - if (profiler) profiler.start(`${contractName}.${methodName}.prove`); - await tx.prove(); - if (profiler) profiler.stop(); - - await tx.sign(signer.map((e) => e.privateKey)).send(); - console.log('DONE!'); - break; // Exit the loop if successful - } catch (error) { - console.error('Error:', error); - retries--; // Decrement the number of retries - if (retries === 0) { - throw error; // Throw the error if no more retries left - } - console.log(`Retrying... (${retries} retries left)`); + if (logMemory) logMemUsage(); + console.log( + `Generate proof and submit tx for ${contractName}.${methodName}()...` + ); + let retries = 1; // Number of retries + + while (retries > 0) { + try { + if (profiler) profiler.start(`${contractName}.${methodName}.prove`); + await tx.prove(); + if (profiler) profiler.stop(); + + await tx.sign(signer.map((e) => e.privateKey)).send(); + console.log('DONE!'); + break; // Exit the loop if successful + } catch (error) { + console.error('Error:', error); + retries--; // Decrement the number of retries + if (retries === 0) { + throw error; // Throw the error if no more retries left + } + console.log(`Retrying... (${retries} retries left)`); + } } - } } export async function fetchAllContract( - contracts: ContractList, - selected: string[], - maxAttempts = 10 + contracts: ContractList, + selected: string[], + maxAttempts = 10 ): Promise { - let attempts = 0; - - while (attempts < maxAttempts) { - try { - const entries = Object.entries(contracts); - for (const [key, { contract }] of entries) { - if (selected.length > 0 && !selected.includes(key)) continue; - const [fetchedActions, fetchedAccount] = await Promise.all([ - Mina.fetchActions(contract.address), - fetchAccount({ publicKey: contract.address }), - ]); - - if (Array.isArray(fetchedActions)) { - contracts[key].actionStates = [ - Reducer.initialActionState, - ...fetchedActions.map((e) => Field(e.hash)), - ]; + let attempts = 0; + + while (attempts < maxAttempts) { + try { + const entries = Object.entries(contracts); + for (const [key, { contract }] of entries) { + if (selected.length > 0 && !selected.includes(key)) continue; + const [fetchedActions, fetchedAccount] = await Promise.all([ + Mina.fetchActions(contract.address), + fetchAccount({ publicKey: contract.address }), + ]); + + if (Array.isArray(fetchedActions)) { + contracts[key].actionStates = [ + Reducer.initialActionState, + ...fetchedActions.map((e) => Field(e.hash)), + ]; + } + } + console.log('Fetch all info success'); + + // If the code succeeds, break out of the loop + break; + } catch (error) { + console.log('Error: ', error); + attempts++; + + // Wait for some time before retrying (e.g., 1 second) + await new Promise((resolve) => setTimeout(resolve, 1000)); } - } - console.log('Fetch all info success'); - - // If the code succeeds, break out of the loop - break; - } catch (error) { - console.log('Error: ', error); - attempts++; - - // Wait for some time before retrying (e.g., 1 second) - await new Promise((resolve) => setTimeout(resolve, 1000)); } - } - if (attempts === maxAttempts) { - console.log('Maximum number of attempts reached. Code failed.'); - } + if (attempts === maxAttempts) { + console.log('Maximum number of attempts reached. Code failed.'); + } - return contracts; + return contracts; } export function logMemUsage() { - console.log( - 'Current memory usage:', - Math.floor(process.memoryUsage().rss / 1024 / 1024), - 'MB' - ); + console.log( + 'Current memory usage:', + Math.floor(process.memoryUsage().rss / 1024 / 1024), + 'MB' + ); } diff --git a/src/scripts/Campaign.test.ts b/src/scripts/Campaign.test.ts index a6260e1..fe6a2dc 100644 --- a/src/scripts/Campaign.test.ts +++ b/src/scripts/Campaign.test.ts @@ -1,30 +1,30 @@ import { - Field, - Reducer, - Mina, - PrivateKey, - PublicKey, - Cache, - SmartContract, + Field, + Reducer, + Mina, + PrivateKey, + PublicKey, + Cache, + SmartContract, } from 'o1js'; import fs from 'fs'; import { getProfiler } from './helper/profiler.js'; import { - CampaignAction, - CampaignContract, - CreateCampaign, - CreateCampaignInput, + CampaignAction, + CampaignContract, + CreateCampaign, + CreateCampaignInput, } from '../contracts/Campaign.js'; import { - InfoStorage as CampaignInfoStorage, - ConfigStorage, - OwnerStorage, - StatusEnum, - StatusStorage, + InfoStorage as CampaignInfoStorage, + ConfigStorage, + OwnerStorage, + StatusEnum, + StatusStorage, } from '../contracts/CampaignStorage.js'; import { - AddressStorage, - EMPTY_ADDRESS_MT, + AddressStorage, + EMPTY_ADDRESS_MT, } from '../contracts/SharedStorage.js'; import { ContractList, compile, deploy, proveAndSend } from '../libs/utils.js'; import { Config, Key } from './helper/config.js'; @@ -36,150 +36,158 @@ import { TreasuryContract } from '../contracts/Treasury.js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; describe('Campaign', () => { - const doProofs = false; - const profiling = false; - const logMemory = false; - const cache = Cache.FileSystem('./caches'); - const CampaignProfiler = getProfiler('Benchmark Campaign'); - const profiler = profiling ? CampaignProfiler : undefined; - let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); - Mina.setActiveInstance(Local); - - let accounts: Key[] = Local.testAccounts.slice(1, 5); - let feePayerKey: Key = accounts[0]; - let contracts: ContractList; - let actions: CampaignAction[]; - - let addressMerkleTree = EMPTY_ADDRESS_MT(); - - let zkAppAddressess = {}; - - // Campaign storage - let campaignInfoStorage = new CampaignInfoStorage(); - let ownerStorage = new OwnerStorage(); - let statusStorage = new StatusStorage(); - let configStorage = new ConfigStorage(); - let campaignAddressStorage = new AddressStorage(addressMerkleTree); - - beforeAll(async () => { - let configJson: Config = JSON.parse(fs.readFileSync('config.json', 'utf8')); - - await Promise.all( - Object.keys(Contract) - .filter((item) => isNaN(Number(item))) - .map(async (e) => { - let config = configJson.deployAliases[e.toLowerCase()]; - // console.log(config); - let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( - fs.readFileSync(config.keyPath, 'utf8') - ); - let key = { - privateKey: PrivateKey.fromBase58(keyBase58.privateKey), - publicKey: PublicKey.fromBase58(keyBase58.publicKey), - }; - let contract = (() => { - switch (e.toLowerCase()) { - case Contract.PROJECT: - return new ProjectContract(key.publicKey); - case Contract.CAMPAIGN: - return new CampaignContract(key.publicKey); - case Contract.FUNDING: - return new FundingContract(key.publicKey); - case Contract.PARTICIPATION: - return new ParticipationContract(key.publicKey); - case Contract.TREASURY: - return new TreasuryContract(key.publicKey); - default: - return new SmartContract(key.publicKey); - } - })(); - - addressMerkleTree.setLeaf( - AddressStorage.calculateIndex(ZkAppEnum[e]).toBigInt(), - AddressStorage.calculateLeaf(key.publicKey) - ); - - contracts[e.toLowerCase()] = { - name: e.toLowerCase(), - key: key, - contract: contract, - actionStates: [Reducer.initialActionState], - }; - }) - ); - }); - - // beforeEach(() => {}); - - it('should compile programs and contracts', async () => { - await compile(CreateCampaign, cache, logMemory, profiler); - if (doProofs) { - await compile(CampaignContract, cache, logMemory, profiler); - } else { - console.log('CampaignContract.analyzeMethods...'); - CampaignContract.analyzeMethods(); - } - }); - - it('should deploy contracts', async () => { - await deploy(contracts[Contract.CAMPAIGN], [], feePayerKey); - }); - - it('should create a campaign', async () => { - let createCampaignInput = new CreateCampaignInput({ - ipfsHash: IPFSHash.fromString('test'), - committeeId: Field(0), - keyId: Field(0), + const doProofs = false; + const profiling = false; + const logMemory = false; + const cache = Cache.FileSystem('./caches'); + const CampaignProfiler = getProfiler('Benchmark Campaign'); + const profiler = profiling ? CampaignProfiler : undefined; + let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); + Mina.setActiveInstance(Local); + + let accounts: Key[] = Local.testAccounts.slice(1, 5); + let feePayerKey: Key = accounts[0]; + let contracts: ContractList; + let actions: CampaignAction[]; + + let zkAppAddressess = {}; + + // Campaign storage + let campaignInfoStorage = new CampaignInfoStorage(); + let ownerStorage = new OwnerStorage(); + let statusStorage = new StatusStorage(); + let configStorage = new ConfigStorage(); + let campaignAddressStorage = new AddressStorage(); + + beforeAll(async () => { + let configJson: Config = JSON.parse( + fs.readFileSync('config.json', 'utf8') + ); + + await Promise.all( + Object.keys(Contract) + .filter((item) => isNaN(Number(item))) + .map(async (e) => { + let config = configJson.deployAliases[e.toLowerCase()]; + // console.log(config); + let keyBase58: { privateKey: string; publicKey: string } = + JSON.parse(fs.readFileSync(config.keyPath, 'utf8')); + let key = { + privateKey: PrivateKey.fromBase58(keyBase58.privateKey), + publicKey: PublicKey.fromBase58(keyBase58.publicKey), + }; + let contract = (() => { + switch (e.toLowerCase()) { + case Contract.PROJECT: + return new ProjectContract(key.publicKey); + case Contract.CAMPAIGN: + return new CampaignContract(key.publicKey); + case Contract.FUNDING: + return new FundingContract(key.publicKey); + case Contract.PARTICIPATION: + return new ParticipationContract(key.publicKey); + case Contract.TREASURY: + return new TreasuryContract(key.publicKey); + default: + return new SmartContract(key.publicKey); + } + })(); + + campaignAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + contracts[e.toLowerCase()] = { + name: e.toLowerCase(), + key: key, + contract: contract, + actionStates: [Reducer.initialActionState], + }; + }) + ); }); - let campaignContract = contracts[Contract.CAMPAIGN] - .contract as CampaignContract; + // beforeEach(() => {}); - let tx = await Mina.transaction(feePayerKey.publicKey, () => { - campaignContract.createCampaign(createCampaignInput); + it('should compile programs and contracts', async () => { + await compile(CreateCampaign, cache, logMemory, profiler); + if (doProofs) { + await compile(CampaignContract, cache, logMemory, profiler); + } else { + console.log('CampaignContract.analyzeMethods...'); + CampaignContract.analyzeMethods(); + } }); - await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'createProject'); - - actions.push( - new CampaignAction({ - campaignId: Field(-1), - ipfsHash: createCampaignInput.ipfsHash, - owner: feePayerKey.publicKey, - campaignStatus: Field(StatusEnum.APPLICATION), - committeeId: createCampaignInput.committeeId, - keyId: createCampaignInput.keyId, - }) - ); - }); - - it('should update campaigns', async () => { - let campaignContract = contracts[Contract.CAMPAIGN] - .contract as CampaignContract; - - let campaignId = Field(0); - - let campaignProof = await CreateCampaign.firstStep( - campaignContract.ownerTreeRoot.get(), - campaignContract.infoTreeRoot.get(), - campaignContract.statusTreeRoot.get(), - campaignContract.configTreeRoot.get(), - campaignContract.nextCampaignId.get(), - campaignContract.lastRolledUpActionState.get() - ); - - campaignProof = await CreateCampaign.createCampaign( - campaignProof, - actions[0], - ownerStorage.getWitness(ownerStorage.calculateLevel1Index(campaignId)), - campaignInfoStorage.getWitness( - campaignInfoStorage.calculateLevel1Index(campaignId) - ), - statusStorage.getWitness(statusStorage.calculateLevel1Index(campaignId)), - // ?? - configStorage.getLevel1Witness( - configStorage.calculateLevel1Index(Field(0)) - ) - ); - }); + it('should deploy contracts', async () => { + await deploy(contracts[Contract.CAMPAIGN], [], feePayerKey); + }); + + it('should create a campaign', async () => { + let createCampaignInput = new CreateCampaignInput({ + ipfsHash: IPFSHash.fromString('test'), + committeeId: Field(0), + keyId: Field(0), + }); + + let campaignContract = contracts[Contract.CAMPAIGN] + .contract as CampaignContract; + + let tx = await Mina.transaction(feePayerKey.publicKey, () => { + campaignContract.createCampaign(createCampaignInput); + }); + + await proveAndSend( + tx, + [feePayerKey], + 'ProjectContract', + 'createProject' + ); + + actions.push( + new CampaignAction({ + campaignId: Field(-1), + ipfsHash: createCampaignInput.ipfsHash, + owner: feePayerKey.publicKey, + campaignStatus: Field(StatusEnum.APPLICATION), + committeeId: createCampaignInput.committeeId, + keyId: createCampaignInput.keyId, + }) + ); + }); + + it('should update campaigns', async () => { + let campaignContract = contracts[Contract.CAMPAIGN] + .contract as CampaignContract; + + let campaignId = Field(0); + + let campaignProof = await CreateCampaign.firstStep( + campaignContract.ownerTreeRoot.get(), + campaignContract.infoTreeRoot.get(), + campaignContract.statusTreeRoot.get(), + campaignContract.configTreeRoot.get(), + campaignContract.nextCampaignId.get(), + campaignContract.lastRolledUpActionState.get() + ); + + campaignProof = await CreateCampaign.createCampaign( + campaignProof, + actions[0], + ownerStorage.getWitness( + ownerStorage.calculateLevel1Index(campaignId) + ), + campaignInfoStorage.getWitness( + campaignInfoStorage.calculateLevel1Index(campaignId) + ), + statusStorage.getWitness( + statusStorage.calculateLevel1Index(campaignId) + ), + // ?? + configStorage.getLevel1Witness( + configStorage.calculateLevel1Index(Field(0)) + ) + ); + }); }); diff --git a/src/scripts/Funding.test.ts b/src/scripts/Funding.test.ts index f540426..5ddd7f2 100644 --- a/src/scripts/Funding.test.ts +++ b/src/scripts/Funding.test.ts @@ -1,340 +1,353 @@ import { - Field, - Reducer, - Mina, - PrivateKey, - PublicKey, - AccountUpdate, - Poseidon, - MerkleMap, - MerkleTree, - MerkleWitness, - Proof, - Void, - Cache, - SmartContract, - Scalar, - Account, - Provable, + Field, + Reducer, + Mina, + PrivateKey, + PublicKey, + AccountUpdate, + Poseidon, + MerkleMap, + MerkleTree, + MerkleWitness, + Proof, + Void, + Cache, + SmartContract, + Scalar, + Account, + Provable, } from 'o1js'; import fs from 'fs/promises'; import { getProfiler } from './helper/profiler.js'; import randomAccounts from './helper/randomAccounts.js'; import { - FundingContract, - CreateReduceProof, - CreateRollupProof, - FundingInput, - FundingAction, + FundingContract, + CreateReduceProof, + CreateRollupProof, + FundingInput, + FundingAction, } from '../contracts/Funding.js'; import { RequestIdStorage, ValueStorage } from '../contracts/FundingStorage.js'; import { Key, Config } from './helper/config.js'; import { - AddressStorage, - EMPTY_ADDRESS_MT, - ReduceStorage, - getZkAppRef, - ActionStatus, + AddressStorage, + EMPTY_ADDRESS_MT, + ReduceStorage, + getZkAppRef, + ActionStatus, } from '../contracts/SharedStorage.js'; import { Contract, ZkAppEnum } from '../constants.js'; import { - ContractList, - deploy, - proveAndSend, - fetchAllContract, + ContractList, + deploy, + proveAndSend, + fetchAllContract, } from '../libs/utils.js'; import { CustomScalar } from '@auxo-dev/auxo-libs'; import { CustomScalarArray, ZkApp } from '@auxo-dev/dkg'; import { TreasuryContract, ClaimFund } from '../contracts/Treasury.js'; describe('Funding', () => { - const doProofs = false; - const cache = Cache.FileSystem('./caches'); - - let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); - Mina.setActiveInstance(Local); - - let feePayerKey: Key = Local.testAccounts[0]; - let contracts: ContractList = {}; - let addressMerkleTree = EMPTY_ADDRESS_MT(); - let tx; - // Funding storage - let fundingReduceStorage = new ReduceStorage(); - let sumRStorage = new ValueStorage(); - let sumMStorage = new ValueStorage(); - let requestIdStorage = new RequestIdStorage(); - let fundingAddressStorage = new AddressStorage(addressMerkleTree); - let fundingAction: FundingAction[] = []; - let fundingInput: FundingInput[]; - let index: number; - let fundingActionStates: Field[]; - let secretVectors: CustomScalarArray[] = [ - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(10n)), - CustomScalar.fromScalar(Scalar.from(10n)), - CustomScalar.fromScalar(Scalar.from(50n)), - CustomScalar.fromScalar(Scalar.from(0n)), - ]), - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(10n)), - CustomScalar.fromScalar(Scalar.from(10n)), - CustomScalar.fromScalar(Scalar.from(0n)), - CustomScalar.fromScalar(Scalar.from(10n)), - ]), - ]; - - let randomsVectors: CustomScalarArray[] = [ - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(100n)), - CustomScalar.fromScalar(Scalar.from(200n)), - CustomScalar.fromScalar(Scalar.from(300n)), - CustomScalar.fromScalar(Scalar.from(400n)), - ]), - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(500n)), - CustomScalar.fromScalar(Scalar.from(600n)), - CustomScalar.fromScalar(Scalar.from(700n)), - CustomScalar.fromScalar(Scalar.from(800n)), - ]), - ]; - - let investors: Key[] = [ - { - privateKey: Local.testAccounts[1].privateKey, - publicKey: Local.testAccounts[1].publicKey, - }, - { - privateKey: Local.testAccounts[2].privateKey, - publicKey: Local.testAccounts[2].publicKey, - }, - ]; - - beforeAll(async () => { - let configJson: Config = JSON.parse( - await fs.readFile('config.json', 'utf8') - ); - await Promise.all( - Object.keys(Contract) - .filter((item) => isNaN(Number(item))) - .map(async (e) => { - let config = configJson.deployAliases[e.toLowerCase()]; - // console.log(config); - let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.keyPath, 'utf8') - ); - let key = { - privateKey: PrivateKey.fromBase58(keyBase58.privateKey), - publicKey: PublicKey.fromBase58(keyBase58.publicKey), - }; - let contract = (() => { - switch (e.toLowerCase()) { - case Contract.FUNDING: - return new FundingContract(key.publicKey); - case Contract.TREASURY: - return new TreasuryContract(key.publicKey); - case Contract.REQUEST: - return new ZkApp.Request.RequestContract(key.publicKey); - default: - return new SmartContract(key.publicKey); - } - })(); - - addressMerkleTree.setLeaf( - AddressStorage.calculateIndex(ZkAppEnum[e]).toBigInt(), - AddressStorage.calculateLeaf(key.publicKey) - ); - - contracts[e.toLowerCase()] = { - name: e.toLowerCase(), - key: key, - contract: contract, - actionStates: [Reducer.initialActionState], - }; - }) - ); - - fundingAddressStorage = new AddressStorage(addressMerkleTree); - }); - - // beforeEach(() => {}); - - it('compile proof', async () => { - console.log('CreateReduceProof.compile...'); - await CreateReduceProof.compile(); - console.log('CreateRollupProof.compile...'); - await CreateRollupProof.compile(); - if (doProofs) { - console.log('FundingContract.compile...'); - await FundingContract.compile(); - await ClaimFund.compile(); - await TreasuryContract.compile(); - } else { - console.log('FundingContract.analyzeMethods...'); - FundingContract.analyzeMethods(); - } - }); - - it('Deploy and funding', async () => { - await deploy( - contracts[Contract.FUNDING], - [['zkApps', fundingAddressStorage.addresses.getRoot()]], - feePayerKey - ); - await deploy(contracts[Contract.TREASURY], [], feePayerKey); - await deploy(contracts[Contract.REQUEST], [], feePayerKey); - - console.log('Funding...'); - - let fundingContract = contracts[Contract.FUNDING] - .contract as FundingContract; - - fundingInput = [ - new FundingInput({ - campaignId: Field(1), - committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, - secretVector: secretVectors[0], - random: randomsVectors[0], - treasuryContract: getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.TREASURY, - contracts[Contract.TREASURY].contract.address - ), - }), - new FundingInput({ - campaignId: Field(1), - committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, - secretVector: secretVectors[1], - random: randomsVectors[1], - treasuryContract: getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.TREASURY, - contracts[Contract.TREASURY].contract.address - ), - }), + const doProofs = false; + const cache = Cache.FileSystem('./caches'); + + let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); + Mina.setActiveInstance(Local); + + let feePayerKey: Key = Local.testAccounts[0]; + let contracts: ContractList = {}; + let tx; + // Funding storage + let fundingReduceStorage = new ReduceStorage(); + let sumRStorage = new ValueStorage(); + let sumMStorage = new ValueStorage(); + let requestIdStorage = new RequestIdStorage(); + let fundingAddressStorage = new AddressStorage(); + let fundingAction: FundingAction[] = []; + let fundingInput: FundingInput[]; + let index: number; + let fundingActionStates: Field[]; + let secretVectors: CustomScalarArray[] = [ + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(10n)), + CustomScalar.fromScalar(Scalar.from(10n)), + CustomScalar.fromScalar(Scalar.from(50n)), + CustomScalar.fromScalar(Scalar.from(0n)), + ]), + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(10n)), + CustomScalar.fromScalar(Scalar.from(10n)), + CustomScalar.fromScalar(Scalar.from(0n)), + CustomScalar.fromScalar(Scalar.from(10n)), + ]), ]; - let result: { - R: ZkApp.Request.RequestVector; - M: ZkApp.Request.RequestVector; - }; - - for (let i = 0; i < investors.length; i++) { - let balanceBefore = Number(Account(investors[i].publicKey).balance.get()); - tx = await Mina.transaction(investors[i].publicKey, () => { - result = fundingContract.fund(fundingInput[i]); - }); - await proveAndSend(tx, [investors[i]], Contract.FUNDING, 'fund'); - let balanceAfter = Number(Account(investors[i].publicKey).balance.get()); - console.log('Balance change: ', balanceBefore - balanceAfter); - - let { R, M } = result!; - - fundingAction.push( - new FundingAction({ - campaignId: fundingInput[i].campaignId, - R, - M, - }) - ); - } - }); - - it('Reduce', async () => { - await fetchAllContract(contracts, [Contract.FUNDING]); - - let fundingContract = contracts[Contract.FUNDING] - .contract as FundingContract; - let lastActionState = fundingContract.actionState.get(); - fundingActionStates = contracts[Contract.FUNDING].actionStates; - index = fundingActionStates.findIndex((obj) => - Boolean(obj.equals(lastActionState)) - ); - Provable.log('lastActionStates: ', lastActionState); - Provable.log('Funding action states: ', fundingActionStates); - Provable.log('Index: ', index); - - console.log('Reduce funding...'); - - console.log('First step: '); - let reduceFundingProof = await CreateReduceProof.firstStep( - fundingContract.actionState.get(), - fundingContract.actionStatus.get() - ); - - console.log('Next step: '); - - for (let i = 0; i < investors.length; i++) { - console.log('Step', i); - reduceFundingProof = await CreateReduceProof.nextStep( - reduceFundingProof, - fundingAction[i], - fundingReduceStorage.getWitness(fundingActionStates[index + 1 + i]) - ); - - // update storage: - fundingReduceStorage.updateLeaf( - fundingReduceStorage.calculateIndex(fundingActionStates[index + 1 + i]), - fundingReduceStorage.calculateLeaf(ActionStatus.REDUCED) - ); - } - - tx = await Mina.transaction(feePayerKey.publicKey, () => { - fundingContract.reduce(reduceFundingProof); + let randomsVectors: CustomScalarArray[] = [ + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(100n)), + CustomScalar.fromScalar(Scalar.from(200n)), + CustomScalar.fromScalar(Scalar.from(300n)), + CustomScalar.fromScalar(Scalar.from(400n)), + ]), + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(500n)), + CustomScalar.fromScalar(Scalar.from(600n)), + CustomScalar.fromScalar(Scalar.from(700n)), + CustomScalar.fromScalar(Scalar.from(800n)), + ]), + ]; + + let investors: Key[] = [ + { + privateKey: Local.testAccounts[1].privateKey, + publicKey: Local.testAccounts[1].publicKey, + }, + { + privateKey: Local.testAccounts[2].privateKey, + publicKey: Local.testAccounts[2].publicKey, + }, + ]; + + beforeAll(async () => { + let configJson: Config = JSON.parse( + await fs.readFile('config.json', 'utf8') + ); + await Promise.all( + Object.keys(Contract) + .filter((item) => isNaN(Number(item))) + .map(async (e) => { + let config = configJson.deployAliases[e.toLowerCase()]; + // console.log(config); + let keyBase58: { privateKey: string; publicKey: string } = + JSON.parse(await fs.readFile(config.keyPath, 'utf8')); + let key = { + privateKey: PrivateKey.fromBase58(keyBase58.privateKey), + publicKey: PublicKey.fromBase58(keyBase58.publicKey), + }; + let contract = (() => { + switch (e.toLowerCase()) { + case Contract.FUNDING: + return new FundingContract(key.publicKey); + case Contract.TREASURY: + return new TreasuryContract(key.publicKey); + case Contract.REQUEST: + return new ZkApp.Request.RequestContract( + key.publicKey + ); + default: + return new SmartContract(key.publicKey); + } + })(); + + fundingAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + contracts[e.toLowerCase()] = { + name: e.toLowerCase(), + key: key, + contract: contract, + actionStates: [Reducer.initialActionState], + }; + }) + ); + }); + + // beforeEach(() => {}); + + it('compile proof', async () => { + console.log('CreateReduceProof.compile...'); + await CreateReduceProof.compile(); + console.log('CreateRollupProof.compile...'); + await CreateRollupProof.compile(); + if (doProofs) { + console.log('FundingContract.compile...'); + await FundingContract.compile(); + await ClaimFund.compile(); + await TreasuryContract.compile(); + } else { + console.log('FundingContract.analyzeMethods...'); + FundingContract.analyzeMethods(); + } }); - await proveAndSend(tx, [feePayerKey], Contract.FUNDING, 'reduce'); - }); - - it('RollUp', async () => { - let fundingContract = contracts[Contract.FUNDING] - .contract as FundingContract; - console.log('RollUp funding...'); - - await fetchAllContract(contracts, [Contract.REQUEST]); - - let rollUpFundingProof = await CreateRollupProof.firstStep( - fundingAction[0].campaignId, - secretVectors[0].length, - fundingContract.actionStatus.get() - ); - - for (let i = 0; i < investors.length; i++) { - console.log('Step', i); - rollUpFundingProof = await CreateRollupProof.nextStep( - rollUpFundingProof, - fundingAction[i], - fundingActionStates[index + i], - fundingReduceStorage.getWitness(fundingActionStates[index + 1 + i]) - ); - - // update storage: - fundingReduceStorage.updateLeaf( - fundingReduceStorage.calculateIndex(fundingActionStates[index + 1 + i]), - fundingReduceStorage.calculateLeaf(ActionStatus.ROLL_UPED) - ); - } - - tx = await Mina.transaction(feePayerKey.publicKey, () => { - fundingContract.rollupRequest( - rollUpFundingProof, - Field(2), - Field(2), - sumRStorage.getLevel1Witness( - sumRStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - sumMStorage.getLevel1Witness( - sumMStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - requestIdStorage.getLevel1Witness( - requestIdStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.REQUEST, - contracts[Contract.REQUEST].contract.address - ) - ); + + it('Deploy and funding', async () => { + await deploy( + contracts[Contract.FUNDING], + [['zkApps', fundingAddressStorage.root]], + feePayerKey + ); + await deploy(contracts[Contract.TREASURY], [], feePayerKey); + await deploy(contracts[Contract.REQUEST], [], feePayerKey); + + console.log('Funding...'); + + let fundingContract = contracts[Contract.FUNDING] + .contract as FundingContract; + + fundingInput = [ + new FundingInput({ + campaignId: Field(1), + committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, + secretVector: secretVectors[0], + random: randomsVectors[0], + treasuryContract: fundingAddressStorage.getZkAppRef( + ZkAppEnum.TREASURY, + contracts[Contract.TREASURY].contract.address + ), + }), + new FundingInput({ + campaignId: Field(1), + committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, + secretVector: secretVectors[1], + random: randomsVectors[1], + treasuryContract: fundingAddressStorage.getZkAppRef( + ZkAppEnum.TREASURY, + contracts[Contract.TREASURY].contract.address + ), + }), + ]; + + let result: { + R: ZkApp.Request.RequestVector; + M: ZkApp.Request.RequestVector; + }; + + for (let i = 0; i < investors.length; i++) { + let balanceBefore = Number( + Account(investors[i].publicKey).balance.get() + ); + tx = await Mina.transaction(investors[i].publicKey, () => { + result = fundingContract.fund(fundingInput[i]); + }); + await proveAndSend(tx, [investors[i]], Contract.FUNDING, 'fund'); + let balanceAfter = Number( + Account(investors[i].publicKey).balance.get() + ); + console.log('Balance change: ', balanceBefore - balanceAfter); + + let { R, M } = result!; + + fundingAction.push( + new FundingAction({ + campaignId: fundingInput[i].campaignId, + R, + M, + }) + ); + } + }); + + it('Reduce', async () => { + await fetchAllContract(contracts, [Contract.FUNDING]); + + let fundingContract = contracts[Contract.FUNDING] + .contract as FundingContract; + let lastActionState = fundingContract.actionState.get(); + fundingActionStates = contracts[Contract.FUNDING].actionStates; + index = fundingActionStates.findIndex((obj) => + Boolean(obj.equals(lastActionState)) + ); + Provable.log('lastActionStates: ', lastActionState); + Provable.log('Funding action states: ', fundingActionStates); + Provable.log('Index: ', index); + + console.log('Reduce funding...'); + + console.log('First step: '); + let reduceFundingProof = await CreateReduceProof.firstStep( + fundingContract.actionState.get(), + fundingContract.actionStatus.get() + ); + + console.log('Next step: '); + + for (let i = 0; i < investors.length; i++) { + console.log('Step', i); + reduceFundingProof = await CreateReduceProof.nextStep( + reduceFundingProof, + fundingAction[i], + fundingReduceStorage.getWitness( + fundingActionStates[index + 1 + i] + ) + ); + + // update storage: + fundingReduceStorage.updateLeaf( + fundingReduceStorage.calculateIndex( + fundingActionStates[index + 1 + i] + ), + fundingReduceStorage.calculateLeaf(ActionStatus.REDUCED) + ); + } + + tx = await Mina.transaction(feePayerKey.publicKey, () => { + fundingContract.reduce(reduceFundingProof); + }); + await proveAndSend(tx, [feePayerKey], Contract.FUNDING, 'reduce'); + }); + + it('RollUp', async () => { + let fundingContract = contracts[Contract.FUNDING] + .contract as FundingContract; + console.log('RollUp funding...'); + + await fetchAllContract(contracts, [Contract.REQUEST]); + + let rollUpFundingProof = await CreateRollupProof.firstStep( + fundingAction[0].campaignId, + secretVectors[0].length, + fundingContract.actionStatus.get() + ); + + for (let i = 0; i < investors.length; i++) { + console.log('Step', i); + rollUpFundingProof = await CreateRollupProof.nextStep( + rollUpFundingProof, + fundingAction[i], + fundingActionStates[index + i], + fundingReduceStorage.getWitness( + fundingActionStates[index + 1 + i] + ) + ); + + // update storage: + fundingReduceStorage.updateLeaf( + fundingReduceStorage.calculateIndex( + fundingActionStates[index + 1 + i] + ), + fundingReduceStorage.calculateLeaf(ActionStatus.ROLL_UPED) + ); + } + + tx = await Mina.transaction(feePayerKey.publicKey, () => { + fundingContract.rollupRequest( + rollUpFundingProof, + Field(2), + Field(2), + sumRStorage.getLevel1Witness( + sumRStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + sumMStorage.getLevel1Witness( + sumMStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + requestIdStorage.getLevel1Witness( + requestIdStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + fundingAddressStorage.getZkAppRef( + ZkAppEnum.REQUEST, + contracts[Contract.REQUEST].contract.address + ) + ); + }); + await proveAndSend(tx, [feePayerKey], Contract.FUNDING, ''); }); - await proveAndSend(tx, [feePayerKey], Contract.FUNDING, ''); - }); }); diff --git a/src/scripts/Participation.test.ts b/src/scripts/Participation.test.ts index 3c36ea9..0702f64 100644 --- a/src/scripts/Participation.test.ts +++ b/src/scripts/Participation.test.ts @@ -1,54 +1,54 @@ import { - Field, - Reducer, - Mina, - PrivateKey, - PublicKey, - AccountUpdate, - Poseidon, - MerkleMap, - MerkleTree, - MerkleWitness, - Proof, - Void, - Cache + Field, + Reducer, + Mina, + PrivateKey, + PublicKey, + AccountUpdate, + Poseidon, + MerkleMap, + MerkleTree, + MerkleWitness, + Proof, + Void, + Cache, } from 'o1js'; import { getProfiler } from './helper/profiler.js'; import randomAccounts from './helper/randomAccounts.js'; import { - ParticipationContract, - JoinCampaign, + ParticipationContract, + JoinCampaign, } from '../contracts/Participation.js'; describe('Participation', () => { - const doProofs = true; - const cache = Cache.FileSystem('./caches'); + const doProofs = true; + const cache = Cache.FileSystem('./caches'); - let { keys, addresses } = randomAccounts('project', 'p1', 'p2'); - let feePayerKey: PrivateKey; - let feePayer: PublicKey; - let committeeContract: ParticipationContract; + let { keys, addresses } = randomAccounts('project', 'p1', 'p2'); + let feePayerKey: PrivateKey; + let feePayer: PublicKey; + let committeeContract: ParticipationContract; - beforeAll(async () => { - let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); - Mina.setActiveInstance(Local); - feePayerKey = Local.testAccounts[0].privateKey; - feePayer = Local.testAccounts[0].publicKey; - committeeContract = new ParticipationContract(addresses.project); - }); + beforeAll(async () => { + let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); + Mina.setActiveInstance(Local); + feePayerKey = Local.testAccounts[0].privateKey; + feePayer = Local.testAccounts[0].publicKey; + committeeContract = new ParticipationContract(addresses.project); + }); - // beforeEach(() => {}); + // beforeEach(() => {}); - it('compile proof', async () => { - console.log('JoinCampaign.compile...'); - await JoinCampaign.compile(); - if (doProofs) { - console.log('ParticipationContract.compile...'); - await ParticipationContract.compile(); - } else { - console.log('ParticipationContract.analyzeMethods...'); - ParticipationContract.analyzeMethods(); - } - }); + it('compile proof', async () => { + console.log('JoinCampaign.compile...'); + await JoinCampaign.compile(); + if (doProofs) { + console.log('ParticipationContract.compile...'); + await ParticipationContract.compile(); + } else { + console.log('ParticipationContract.analyzeMethods...'); + ParticipationContract.analyzeMethods(); + } + }); }); diff --git a/src/scripts/Platform.ts b/src/scripts/Platform.ts index 36d07f8..4f993de 100644 --- a/src/scripts/Platform.ts +++ b/src/scripts/Platform.ts @@ -1,94 +1,94 @@ import { - Cache, - Field, - Mina, - PrivateKey, - Provable, - PublicKey, - Reducer, - SmartContract, - fetchAccount, - Scalar, - Poseidon, - Account, - AccountUpdate, + Cache, + Field, + Mina, + PrivateKey, + Provable, + PublicKey, + Reducer, + SmartContract, + fetchAccount, + Scalar, + Poseidon, + Account, + AccountUpdate, } from 'o1js'; import fs from 'fs/promises'; import { Config, Key } from './helper/config.js'; import { - AddressStorage, - EMPTY_ADDRESS_MT, - ReduceStorage, - getZkAppRef, - ActionStatus, + AddressStorage, + EMPTY_ADDRESS_MT, + ReduceStorage, + getZkAppRef, + ActionStatus, } from '../contracts/SharedStorage.js'; import { ZkAppEnum, Contract } from '../constants.js'; import { getProfiler } from './helper/profiler.js'; import { IPFSHash, CustomScalar } from '@auxo-dev/auxo-libs'; import { - ProjectContract, - CreateProject, - CreateProjectInput, - ProjectAction, + ProjectContract, + CreateProject, + CreateProjectInput, + ProjectAction, } from '../contracts/Project.js'; import { - MemberArray, - MemberStorage, - InfoStorage as ProjectInfoStorage, - AddressStorage as PayeeStorage, - EMPTY_LEVEL_2_TREE, - Level2Witness, + MemberArray, + MemberStorage, + InfoStorage as ProjectInfoStorage, + AddressStorage as PayeeStorage, + EMPTY_LEVEL_2_TREE, + Level2Witness, } from '../contracts/ProjectStorage.js'; import mockProjectIpfs from './mock/projects.js'; import { - CampaignContract, - CreateCampaign, - CreateCampaignInput, - CampaignAction, + CampaignContract, + CreateCampaign, + CreateCampaignInput, + CampaignAction, } from '../contracts/Campaign.js'; import { - InfoStorage as CampaignInfoStorage, - OwnerStorage, - StatusStorage, - ConfigStorage, - StatusEnum, + InfoStorage as CampaignInfoStorage, + OwnerStorage, + StatusStorage, + ConfigStorage, + StatusEnum, } from '../contracts/CampaignStorage.js'; import mockCampaignIpfs from './mock/campaigns.js'; import { - FundingContract, - CreateReduceProof, - CreateRollupProof, - FundingAction, - FundingInput, + FundingContract, + CreateReduceProof, + CreateRollupProof, + FundingAction, + FundingInput, } from '../contracts/Funding.js'; import { RequestIdStorage, ValueStorage } from '../contracts/FundingStorage.js'; import { - ParticipationContract, - JoinCampaign, - ParticipationAction, - JoinCampaignInput, + ParticipationContract, + JoinCampaign, + ParticipationAction, + JoinCampaignInput, } from '../contracts/Participation.js'; import mockParticipationIpfs from './mock/participations.js'; import { - InfoStorage as ParticipationInfoStorage, - CounterStorage, - IndexStorage, - EMPTY_LEVEL_1_TREE, - EMPTY_LEVEL_1_COMBINED_TREE, + InfoStorage as ParticipationInfoStorage, + CounterStorage, + IndexStorage, + EMPTY_LEVEL_1_TREE, + EMPTY_LEVEL_1_COMBINED_TREE, } from '../contracts/ParticipationStorage.js'; import { - TreasuryContract, - ClaimFund, - TreasuryAction, + TreasuryContract, + ClaimFund, + TreasuryAction, } from '../contracts/Treasury.js'; import { ClaimedStorage } from '../contracts/TreasuryStorage.js'; import { - ContractList, - compile, - deploy, - fetchAllContract, - proveAndSend, - wait, + ContractList, + compile, + deploy, + fetchAllContract, + proveAndSend, + wait, } from '../libs/utils.js'; import { CustomScalarArray, ZkApp } from '@auxo-dev/dkg'; import { RequestStorage } from '@auxo-dev/dkg/build/esm/src/contracts/storages.js'; @@ -103,817 +103,873 @@ const isFunding = true; const isTreasury = false; async function main() { - console.time('runTime'); - const doProofs = false; - const logMemory = true; - const cache = Cache.FileSystem('./caches'); - const profiling = false; - const PlatformProfiler = getProfiler('Benchmark Platform'); - const profiler = profiling ? PlatformProfiler : undefined; - - let feePayerKey: Key; - let contracts: ContractList = {}; - - let configJson: Config = JSON.parse(await fs.readFile('config.json', 'utf8')); - - // Testworld + Berkeley - feePayerKey = { - privateKey: PrivateKey.fromBase58( - 'EKEosAyM6Y6TnPVwUaWhE7iUS3v6mwVW7uDnWes7FkYVwQoUwyMR' - ), - publicKey: PublicKey.fromBase58( - 'B62qmtfTkHLzmvoKYcTLPeqvuVatnB6wtnXsP6jrEi6i2eUEjcxWauH' - ), - }; - - console.log('Deployer Public Key: ', feePayerKey.publicKey.toBase58()); - - const fee = 0.101 * 1e9; // in nanomina (1 billion = 1.0 mina) - - const MINAURL = 'https://proxy.berkeley.minaexplorer.com/graphql'; - const ARCHIVEURL = 'https://archive.berkeley.minaexplorer.com'; - - const network = Mina.Network({ - mina: MINAURL, - archive: ARCHIVEURL, - }); - Mina.setActiveInstance(network); - - let feePayerNonce; - let dk = false; - - do { - let sender = await fetchAccount({ publicKey: feePayerKey.publicKey }); - feePayerNonce = Number(sender.account?.nonce) - 1; - if (feePayerNonce) dk = true; - console.log('fetch nonce'); - await wait(1000); // 1s - } while (!dk); - - console.log('Nonce: ', feePayerNonce); - - let addressMerkleTree = EMPTY_ADDRESS_MT(); - - await Promise.all( - Object.keys(Contract) - .filter((item) => isNaN(Number(item))) - .map(async (e) => { - let config = configJson.deployAliases[e.toLowerCase()]; - // console.log(config); - let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile(config.keyPath, 'utf8') - ); - let key = { - privateKey: PrivateKey.fromBase58(keyBase58.privateKey), - publicKey: PublicKey.fromBase58(keyBase58.publicKey), - }; - let contract = (() => { - switch (e.toLowerCase()) { - case Contract.PROJECT: - return new ProjectContract(key.publicKey); - case Contract.CAMPAIGN: - return new CampaignContract(key.publicKey); - case Contract.FUNDING: - return new FundingContract(key.publicKey); - case Contract.PARTICIPATION: - return new ParticipationContract(key.publicKey); - case Contract.TREASURY: - return new TreasuryContract(key.publicKey); - case Contract.REQUEST: - return new ZkApp.Request.RequestContract(key.publicKey); - default: - return new SmartContract(key.publicKey); - } - })(); - - addressMerkleTree.setLeaf( - AddressStorage.calculateIndex(ZkAppEnum[e]).toBigInt(), - AddressStorage.calculateLeaf(key.publicKey) - ); - - contracts[e.toLowerCase()] = { - name: e.toLowerCase(), - key: key, - contract: contract, - actionStates: [Reducer.initialActionState], - }; - }) - ); - // Project storage - let memberStorage = new MemberStorage(); - let projectInfoStorage = new ProjectInfoStorage(); - let payeeStorage = new PayeeStorage(); - let projectActions: ProjectAction[] = []; - - // Campaign storage - let campaignInfoStorage = new CampaignInfoStorage(); - let ownerStorage = new OwnerStorage(); - let statusStorage = new StatusStorage(); - let configStorage = new ConfigStorage(); - let campaignAddressStorage = new AddressStorage(addressMerkleTree); - let campaignActions: CampaignAction[] = []; - - // Participation storage - let participationInfoStorage = new ParticipationInfoStorage(); - let counterStorage = new CounterStorage(); - let indexStorage = new IndexStorage(); - let participationAddressStorage = new AddressStorage(addressMerkleTree); - let participationAction: ParticipationAction[] = []; - - // Funding storage - let fundingReduceStorage = new ReduceStorage(); - let sumRStorage = new ValueStorage(); - let sumMStorage = new ValueStorage(); - let requestIdStorage = new RequestIdStorage(); - let fundingAddressStorage = new AddressStorage(addressMerkleTree); - let fundingAction: FundingAction[] = []; - - // Treasury storage - let claimedStorage = new ClaimedStorage(); - let treasuryAddressStorage = new AddressStorage(addressMerkleTree); - let treasuryAction: TreasuryAction[] = []; - - if (isProject) { - await compile(CreateProject, cache, logMemory, profiler); - await compile(ProjectContract, cache, logMemory, profiler); - } - - if (isCampaign) { - await compile(CreateCampaign, cache, logMemory, profiler); - await compile(CampaignContract, cache, logMemory, profiler); - } - - if (isParticipation) { - await compile(JoinCampaign, cache, logMemory, profiler); - await compile(ParticipationContract, cache, logMemory, profiler); - } - - if (isFunding) { - await compile(ZkApp.Request.CreateRequest, cache, logMemory, profiler); - await compile(ZkApp.Request.RequestContract, cache, logMemory, profiler); - await compile(ClaimFund, cache, logMemory, profiler); - await compile(TreasuryContract, cache, logMemory, profiler); - await compile(CreateReduceProof, cache, logMemory, profiler); - await compile(CreateRollupProof, cache, logMemory, profiler); - await compile(FundingContract, cache, logMemory, profiler); - } - - if (isTreasury) { - await compile(ClaimFund, cache, logMemory, profiler); - await compile(TreasuryContract, cache, logMemory, profiler); - } - - let tx; - - if (isDeploy) { - // // Deploy ProjectContract - // await deploy( - // contracts[Contract.PROJECT], - // [], - // feePayerKey, - // fee, - // ++feePayerNonce - // ); - - // // Deploy CampaignContract - // await deploy( - // contracts[Contract.CAMPAIGN], - // [['zkApps', campaignAddressStorage.addresses.getRoot()]], - // feePayerKey, - // fee, - // ++feePayerNonce - // ); - - // // Deploy ParticipationContract - // await deploy( - // contracts[Contract.PARTICIPATION], - // [['zkApps', participationAddressStorage.addresses.getRoot()]], - // feePayerKey, - // fee, - // ++feePayerNonce - // ); - - // Deploy FundingContract - await deploy( - contracts[Contract.FUNDING], - [['zkApps', fundingAddressStorage.addresses.getRoot()]], - feePayerKey, - fee, - ++feePayerNonce + console.time('runTime'); + const doProofs = false; + const logMemory = true; + const cache = Cache.FileSystem('./caches'); + const profiling = false; + const PlatformProfiler = getProfiler('Benchmark Platform'); + const profiler = profiling ? PlatformProfiler : undefined; + + let feePayerKey: Key; + let contracts: ContractList = {}; + + let configJson: Config = JSON.parse( + await fs.readFile('config.json', 'utf8') ); - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - let feePayerAccount = AccountUpdate.createSigned(feePayerKey.publicKey); - feePayerAccount.send({ - to: contracts[Contract.FUNDING].contract, - amount: 5 * 10 ** 9, - }); // 5 Mina to send request - which cost 1 - } - ); - await tx.sign([feePayerKey.privateKey]).send(); - - // Deploy TreasuryContract - await deploy( - contracts[Contract.TREASURY], - [['zkApps', treasuryAddressStorage.addresses.getRoot()]], - feePayerKey, - fee, - ++feePayerNonce - ); + // Testworld + Berkeley + feePayerKey = { + privateKey: PrivateKey.fromBase58( + 'EKEosAyM6Y6TnPVwUaWhE7iUS3v6mwVW7uDnWes7FkYVwQoUwyMR' + ), + publicKey: PublicKey.fromBase58( + 'B62qmtfTkHLzmvoKYcTLPeqvuVatnB6wtnXsP6jrEi6i2eUEjcxWauH' + ), + }; - // Deploy RequestConctract - await deploy( - contracts[Contract.REQUEST], - [], - feePayerKey, - fee, - ++feePayerNonce + console.log('Deployer Public Key: ', feePayerKey.publicKey.toBase58()); + + const fee = 0.101 * 1e9; // in nanomina (1 billion = 1.0 mina) + + const MINAURL = 'https://proxy.berkeley.minaexplorer.com/graphql'; + const ARCHIVEURL = 'https://archive.berkeley.minaexplorer.com'; + + const network = Mina.Network({ + mina: MINAURL, + archive: ARCHIVEURL, + }); + Mina.setActiveInstance(network); + + let feePayerNonce; + let dk = false; + + do { + let sender = await fetchAccount({ publicKey: feePayerKey.publicKey }); + feePayerNonce = Number(sender.account?.nonce) - 1; + if (feePayerNonce) dk = true; + console.log('fetch nonce'); + await wait(1000); // 1s + } while (!dk); + + console.log('Nonce: ', feePayerNonce); + + // Project storage + let memberStorage = new MemberStorage(); + let projectInfoStorage = new ProjectInfoStorage(); + let payeeStorage = new PayeeStorage(); + let projectActions: ProjectAction[] = []; + + // Campaign storage + let campaignInfoStorage = new CampaignInfoStorage(); + let ownerStorage = new OwnerStorage(); + let statusStorage = new StatusStorage(); + let configStorage = new ConfigStorage(); + let campaignAddressStorage = new AddressStorage(); + let campaignActions: CampaignAction[] = []; + + // Participation storage + let participationInfoStorage = new ParticipationInfoStorage(); + let counterStorage = new CounterStorage(); + let indexStorage = new IndexStorage(); + let participationAddressStorage = new AddressStorage(); + let participationAction: ParticipationAction[] = []; + + // Funding storage + let fundingReduceStorage = new ReduceStorage(); + let sumRStorage = new ValueStorage(); + let sumMStorage = new ValueStorage(); + let requestIdStorage = new RequestIdStorage(); + let fundingAddressStorage = new AddressStorage(); + let fundingAction: FundingAction[] = []; + + // Treasury storage + let claimedStorage = new ClaimedStorage(); + let treasuryAddressStorage = new AddressStorage(); + let treasuryAction: TreasuryAction[] = []; + + await Promise.all( + Object.keys(Contract) + .filter((item) => isNaN(Number(item))) + .map(async (e) => { + let config = configJson.deployAliases[e.toLowerCase()]; + // console.log(config); + let keyBase58: { privateKey: string; publicKey: string } = + JSON.parse(await fs.readFile(config.keyPath, 'utf8')); + let key = { + privateKey: PrivateKey.fromBase58(keyBase58.privateKey), + publicKey: PublicKey.fromBase58(keyBase58.publicKey), + }; + let contract = (() => { + switch (e.toLowerCase()) { + case Contract.PROJECT: + return new ProjectContract(key.publicKey); + case Contract.CAMPAIGN: + return new CampaignContract(key.publicKey); + case Contract.FUNDING: + return new FundingContract(key.publicKey); + case Contract.PARTICIPATION: + return new ParticipationContract(key.publicKey); + case Contract.TREASURY: + return new TreasuryContract(key.publicKey); + case Contract.REQUEST: + return new ZkApp.Request.RequestContract( + key.publicKey + ); + default: + return new SmartContract(key.publicKey); + } + })(); + + campaignAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + participationAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + fundingAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + treasuryAddressStorage.updateAddress( + AddressStorage.calculateIndex(ZkAppEnum[e]), + key.publicKey + ); + + contracts[e.toLowerCase()] = { + name: e.toLowerCase(), + key: key, + contract: contract, + actionStates: [Reducer.initialActionState], + }; + }) ); - // if (isProject) await wait(); - await wait(); - } - - if (isProject) { - await fetchAllContract(contracts, [Contract.PROJECT]); - console.log('Create projects...'); - let numProjects = 5; - let projectContract = contracts[Contract.PROJECT] - .contract as ProjectContract; - let arrayPublicKey = [ - 'B62qjvrida5Kr4rj7f4gDZyG77TdFMp2ntZ9uf5Xzb7iPodykUgwYqm', - 'B62qnhBkHqUeUTmYiAvvGdywce7j5PeTdU6t6mi7UAL8emD3mDPtQW2', - 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S', - 'B62qmtfTkHLzmvoKYcTLPeqvuVatnB6wtnXsP6jrEi6i2eUEjcxWauH', - ].map((e) => PublicKey.fromBase58(e)); - let memberArray = new MemberArray(arrayPublicKey); - - for (let i = 0; i < numProjects; i++) { - let createProjectInput = new CreateProjectInput({ - members: memberArray, - ipfsHash: IPFSHash.fromString(mockProjectIpfs[0]), - payeeAccount: PublicKey.fromBase58( - 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S' - ), - }); - - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - projectContract.createProject(createProjectInput); - } - ); - await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'createProject'); - - projectActions.push( - new ProjectAction({ - projectId: Field(-1), - members: createProjectInput.members, - ipfsHash: createProjectInput.ipfsHash, - payeeAccount: createProjectInput.payeeAccount, - }) - ); + if (isProject) { + await compile(CreateProject, cache, logMemory, profiler); + await compile(ProjectContract, cache, logMemory, profiler); } - await wait(); - await fetchAllContract(contracts, [Contract.PROJECT]); + if (isCampaign) { + await compile(CreateCampaign, cache, logMemory, profiler); + await compile(CampaignContract, cache, logMemory, profiler); + } - console.log('Reduce projects...'); + if (isParticipation) { + await compile(JoinCampaign, cache, logMemory, profiler); + await compile(ParticipationContract, cache, logMemory, profiler); + } - let createProjectProof = await CreateProject.firstStep( - projectContract.nextProjectId.get(), - projectContract.memberTreeRoot.get(), - projectContract.projectInfoTreeRoot.get(), - projectContract.payeeTreeRoot.get(), - projectContract.lastRolledUpActionState.get() - ); + if (isFunding) { + await compile(ZkApp.Request.CreateRequest, cache, logMemory, profiler); + await compile( + ZkApp.Request.RequestContract, + cache, + logMemory, + profiler + ); + await compile(ClaimFund, cache, logMemory, profiler); + await compile(TreasuryContract, cache, logMemory, profiler); + await compile(CreateReduceProof, cache, logMemory, profiler); + await compile(CreateRollupProof, cache, logMemory, profiler); + await compile(FundingContract, cache, logMemory, profiler); + } - let tree1 = EMPTY_LEVEL_2_TREE(); - for (let i = 0; i < Number(memberArray.length); i++) { - tree1.setLeaf(BigInt(i), MemberArray.hash(memberArray.get(Field(i)))); + if (isTreasury) { + await compile(ClaimFund, cache, logMemory, profiler); + await compile(TreasuryContract, cache, logMemory, profiler); } - for (let i = 0; i < numProjects; i++) { - console.log('Step', i); - createProjectProof = await CreateProject.nextStep( - createProjectProof, - projectActions[i], - memberStorage.getLevel1Witness( - memberStorage.calculateLevel1Index(Field(i)) - ), - projectInfoStorage.getLevel1Witness( - projectInfoStorage.calculateLevel1Index(Field(i)) - ), - payeeStorage.getLevel1Witness( - payeeStorage.calculateLevel1Index(Field(i)) - ) - ); - - // update storage: - memberStorage.updateInternal(Field(i), tree1); - projectInfoStorage.updateLeaf( - projectInfoStorage.calculateLeaf(projectActions[i].ipfsHash), - Field(i) - ); - payeeStorage.updateLeaf( - payeeStorage.calculateLeaf(projectActions[i].payeeAccount), - Field(i) - ); + let tx; + + if (isDeploy) { + // // Deploy ProjectContract + // await deploy( + // contracts[Contract.PROJECT], + // [], + // feePayerKey, + // fee, + // ++feePayerNonce + // ); + + // // Deploy CampaignContract + // await deploy( + // contracts[Contract.CAMPAIGN], + // [['zkApps', campaignAddressStorage.root]], + // feePayerKey, + // fee, + // ++feePayerNonce + // ); + + // // Deploy ParticipationContract + // await deploy( + // contracts[Contract.PARTICIPATION], + // [['zkApps', participationAddressStorage.root]], + // feePayerKey, + // fee, + // ++feePayerNonce + // ); + + // Deploy FundingContract + await deploy( + contracts[Contract.FUNDING], + [['zkApps', fundingAddressStorage.root]], + feePayerKey, + fee, + ++feePayerNonce + ); + + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + let feePayerAccount = AccountUpdate.createSigned( + feePayerKey.publicKey + ); + feePayerAccount.send({ + to: contracts[Contract.FUNDING].contract, + amount: 5 * 10 ** 9, + }); // 5 Mina to send request - which cost 1 + } + ); + await tx.sign([feePayerKey.privateKey]).send(); + + // Deploy TreasuryContract + await deploy( + contracts[Contract.TREASURY], + [['zkApps', treasuryAddressStorage.root]], + feePayerKey, + fee, + ++feePayerNonce + ); + + // Deploy RequestConctract + await deploy( + contracts[Contract.REQUEST], + [], + feePayerKey, + fee, + ++feePayerNonce + ); + + // if (isProject) await wait(); + await wait(); } - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - projectContract.rollup(createProjectProof); - } - ); - await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'rollup'); - - if (isCampaign) await wait(); - } - - if (isCampaign) { - await fetchAllContract(contracts, [Contract.CAMPAIGN]); - console.log('Create campaign...'); - let numCampaign = 3; - let campaignContract = contracts[Contract.CAMPAIGN] - .contract as CampaignContract; - - for (let i = 0; i < numCampaign; i++) { - let createCampaignInput = new CreateCampaignInput({ - ipfsHash: IPFSHash.fromString(mockCampaignIpfs[0]), - committeeId: Field(i + 1), - keyId: Field(i + 1), - }); - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - campaignContract.createCampaign(createCampaignInput); + if (isProject) { + await fetchAllContract(contracts, [Contract.PROJECT]); + console.log('Create projects...'); + let numProjects = 5; + let projectContract = contracts[Contract.PROJECT] + .contract as ProjectContract; + let arrayPublicKey = [ + 'B62qjvrida5Kr4rj7f4gDZyG77TdFMp2ntZ9uf5Xzb7iPodykUgwYqm', + 'B62qnhBkHqUeUTmYiAvvGdywce7j5PeTdU6t6mi7UAL8emD3mDPtQW2', + 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S', + 'B62qmtfTkHLzmvoKYcTLPeqvuVatnB6wtnXsP6jrEi6i2eUEjcxWauH', + ].map((e) => PublicKey.fromBase58(e)); + let memberArray = new MemberArray(arrayPublicKey); + + for (let i = 0; i < numProjects; i++) { + let createProjectInput = new CreateProjectInput({ + members: memberArray, + ipfsHash: IPFSHash.fromString(mockProjectIpfs[0]), + payeeAccount: PublicKey.fromBase58( + 'B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S' + ), + }); + + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + projectContract.createProject(createProjectInput); + } + ); + await proveAndSend( + tx, + [feePayerKey], + 'ProjectContract', + 'createProject' + ); + + projectActions.push( + new ProjectAction({ + projectId: Field(-1), + members: createProjectInput.members, + ipfsHash: createProjectInput.ipfsHash, + payeeAccount: createProjectInput.payeeAccount, + }) + ); } - ); - await proveAndSend( - tx, - [feePayerKey], - Contract.CAMPAIGN, - 'createCampaign' - ); - - campaignActions.push( - new CampaignAction({ - campaignId: Field(-1), - ipfsHash: createCampaignInput.ipfsHash, - owner: feePayerKey.publicKey, - campaignStatus: Field(StatusEnum.APPLICATION), - committeeId: createCampaignInput.committeeId, - keyId: createCampaignInput.keyId, - }) - ); - } - await wait(); - await fetchAllContract(contracts, [Contract.CAMPAIGN]); + await wait(); + await fetchAllContract(contracts, [Contract.PROJECT]); - console.log('Reduce campaign...'); + console.log('Reduce projects...'); - let createCampaignProof = await CreateCampaign.firstStep( - campaignContract.ownerTreeRoot.get(), - campaignContract.infoTreeRoot.get(), - campaignContract.statusTreeRoot.get(), - campaignContract.configTreeRoot.get(), - campaignContract.nextCampaignId.get(), - campaignContract.lastRolledUpActionState.get() - ); + let createProjectProof = await CreateProject.firstStep( + projectContract.nextProjectId.get(), + projectContract.memberTreeRoot.get(), + projectContract.projectInfoTreeRoot.get(), + projectContract.payeeTreeRoot.get(), + projectContract.lastRolledUpActionState.get() + ); - for (let i = 0; i < numCampaign; i++) { - console.log('Step', i); - createCampaignProof = await CreateCampaign.createCampaign( - createCampaignProof, - campaignActions[i], - ownerStorage.getLevel1Witness( - ownerStorage.calculateLevel1Index(Field(i)) - ), - campaignInfoStorage.getLevel1Witness( - campaignInfoStorage.calculateLevel1Index(Field(i)) - ), - statusStorage.getLevel1Witness( - statusStorage.calculateLevel1Index(Field(i)) - ), - configStorage.getLevel1Witness( - configStorage.calculateLevel1Index(Field(i)) - ) - ); - - // update storage: - ownerStorage.updateLeaf( - ownerStorage.calculateLeaf(campaignActions[i].owner), - Field(i) - ); - campaignInfoStorage.updateLeaf( - campaignInfoStorage.calculateLeaf(campaignActions[i].ipfsHash), - Field(i) - ); - statusStorage.updateLeaf( - statusStorage.calculateLeaf(StatusEnum.APPLICATION), - Field(i) - ); - configStorage.updateLeaf( - configStorage.calculateLeaf({ - committeeId: campaignActions[i].committeeId, - keyId: campaignActions[i].keyId, - }), - Field(i) - ); - } + let tree1 = EMPTY_LEVEL_2_TREE(); + for (let i = 0; i < Number(memberArray.length); i++) { + tree1.setLeaf( + BigInt(i), + MemberArray.hash(memberArray.get(Field(i))) + ); + } - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - campaignContract.rollup(createCampaignProof); - } - ); - await proveAndSend(tx, [feePayerKey], Contract.CAMPAIGN, 'rollup'); - - if (isParticipation) await wait(); - } - - if (isParticipation) { - await fetchAllContract(contracts, [Contract.PARTICIPATION]); - console.log('Join campaign...'); - let numCampaign = 2; - let participationContract = contracts[Contract.PARTICIPATION] - .contract as ParticipationContract; - - Provable.log('Onchain: ', participationContract.indexTreeRoot.get()); - - let joinCampaignInput = [ - new JoinCampaignInput({ - campaignId: Field(1), - projectId: Field(1), - participationInfo: IPFSHash.fromString(mockParticipationIpfs[0]), - indexWitness: indexStorage.getWitness( - indexStorage.calculateLevel1Index({ - campaignId: Field(1), - projectId: Field(1), - }) - ), - memberLv1Witness: memberStorage.getLevel1Witness(Field(1)), - memberLv2Witness: new Level2Witness( - EMPTY_LEVEL_2_TREE().getWitness(0n) - ), // temp value since contract hasn't check this - // memberLv2Witness: memberStorage.getLevel2Witness(Field(1), Field(0)), // Field 0 = owner - projectRef: getZkAppRef( - participationAddressStorage.addresses, - ZkAppEnum.PROJECT, - contracts[Contract.PROJECT].contract.address - ), - }), - new JoinCampaignInput({ - campaignId: Field(1), - projectId: Field(2), - participationInfo: IPFSHash.fromString(mockParticipationIpfs[0]), - indexWitness: indexStorage.getWitness( - indexStorage.calculateLevel1Index({ - campaignId: Field(1), - projectId: Field(2), - }) - ), - memberLv1Witness: memberStorage.getLevel1Witness(Field(2)), - memberLv2Witness: new Level2Witness( - EMPTY_LEVEL_2_TREE().getWitness(0n) - ), // fake value since contract hasn't check this - // memberLv2Witness: memberStorage.getLevel2Witness(Field(2), Field(0)), // Field 0 = owner - projectRef: getZkAppRef( - participationAddressStorage.addresses, - ZkAppEnum.PROJECT, - contracts[Contract.PROJECT].contract.address - ), - }), - ]; - - for (let i = 0; i < numCampaign; i++) { - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - participationContract.joinCampaign(joinCampaignInput[i]); + for (let i = 0; i < numProjects; i++) { + console.log('Step', i); + createProjectProof = await CreateProject.nextStep( + createProjectProof, + projectActions[i], + memberStorage.getLevel1Witness( + memberStorage.calculateLevel1Index(Field(i)) + ), + projectInfoStorage.getLevel1Witness( + projectInfoStorage.calculateLevel1Index(Field(i)) + ), + payeeStorage.getLevel1Witness( + payeeStorage.calculateLevel1Index(Field(i)) + ) + ); + + // update storage: + memberStorage.updateInternal(Field(i), tree1); + projectInfoStorage.updateLeaf( + { level1Index: Field(i) }, + projectInfoStorage.calculateLeaf(projectActions[i].ipfsHash) + ); + payeeStorage.updateLeaf( + { level1Index: Field(i) }, + payeeStorage.calculateLeaf(projectActions[i].payeeAccount) + ); } - ); - await proveAndSend( - tx, - [feePayerKey], - Contract.PARTICIPATION, - 'joinCampaign' - ); - - participationAction.push( - new ParticipationAction({ - campaignId: joinCampaignInput[i].campaignId, - projectId: joinCampaignInput[i].projectId, - participationInfo: joinCampaignInput[i].participationInfo, - curApplicationInfoHash: Field(0), - }) - ); + + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + projectContract.rollup(createProjectProof); + } + ); + await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'rollup'); + + if (isCampaign) await wait(); } - await wait(); + if (isCampaign) { + await fetchAllContract(contracts, [Contract.CAMPAIGN]); + console.log('Create campaign...'); + let numCampaign = 3; + let campaignContract = contracts[Contract.CAMPAIGN] + .contract as CampaignContract; + + for (let i = 0; i < numCampaign; i++) { + let createCampaignInput = new CreateCampaignInput({ + ipfsHash: IPFSHash.fromString(mockCampaignIpfs[0]), + committeeId: Field(i + 1), + keyId: Field(i + 1), + }); + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + campaignContract.createCampaign(createCampaignInput); + } + ); + await proveAndSend( + tx, + [feePayerKey], + Contract.CAMPAIGN, + 'createCampaign' + ); + + campaignActions.push( + new CampaignAction({ + campaignId: Field(-1), + ipfsHash: createCampaignInput.ipfsHash, + owner: feePayerKey.publicKey, + campaignStatus: Field(StatusEnum.APPLICATION), + committeeId: createCampaignInput.committeeId, + keyId: createCampaignInput.keyId, + }) + ); + } - console.log('Reduce participation...'); + await wait(); + await fetchAllContract(contracts, [Contract.CAMPAIGN]); - let joinCampaignProof = await JoinCampaign.firstStep( - participationContract.indexTreeRoot.get(), - participationContract.infoTreeRoot.get(), - participationContract.counterTreeRoot.get(), - participationContract.lastRolledUpActionState.get() - ); + console.log('Reduce campaign...'); - for (let i = 0; i < numCampaign; i++) { - console.log('Step', i); - - let witness = indexStorage.getLevel1Witness( - indexStorage.calculateLevel1Index({ - campaignId: participationAction[i].campaignId, - projectId: participationAction[i].projectId, - }) - ); - - let rootFormWitness = witness.calculateRoot(Field(i + 1)); - let rootFormWitnessBEF = witness.calculateRoot(Field(i)); - - joinCampaignProof = await JoinCampaign.joinCampaign( - joinCampaignProof, - participationAction[i], - indexStorage.getLevel1Witness( - indexStorage.calculateLevel1Index({ - campaignId: participationAction[i].campaignId, - projectId: participationAction[i].projectId, - }) - ), - participationInfoStorage.getLevel1Witness( - participationInfoStorage.calculateLevel1Index({ - campaignId: participationAction[i].campaignId, - projectId: participationAction[i].projectId, - }) - ), - Field(i), // current couter of each campaign is 0 - counterStorage.getLevel1Witness( - counterStorage.calculateLevel1Index(participationAction[i].campaignId) - ) - ); - - // update storage: - indexStorage.updateLeaf( - indexStorage.calculateLeaf(Field(i + 1)), // index start from 1 - indexStorage.calculateLevel1Index({ - campaignId: participationAction[i].campaignId, - projectId: participationAction[i].projectId, - }) - ); - - Provable.log('index off aft: ', indexStorage.level1.getRoot()); - - participationInfoStorage.updateLeaf( - participationInfoStorage.calculateLeaf( - participationAction[i].participationInfo - ), - participationInfoStorage.calculateLevel1Index({ - campaignId: participationAction[i].campaignId, - projectId: participationAction[i].projectId, - }) - ); - counterStorage.updateLeaf( - counterStorage.calculateLeaf(Field(i + 1)), - counterStorage.calculateLevel1Index(participationAction[i].campaignId) - ); + let createCampaignProof = await CreateCampaign.firstStep( + campaignContract.ownerTreeRoot.get(), + campaignContract.infoTreeRoot.get(), + campaignContract.statusTreeRoot.get(), + campaignContract.configTreeRoot.get(), + campaignContract.nextCampaignId.get(), + campaignContract.lastRolledUpActionState.get() + ); + + for (let i = 0; i < numCampaign; i++) { + console.log('Step', i); + createCampaignProof = await CreateCampaign.createCampaign( + createCampaignProof, + campaignActions[i], + ownerStorage.getLevel1Witness( + ownerStorage.calculateLevel1Index(Field(i)) + ), + campaignInfoStorage.getLevel1Witness( + campaignInfoStorage.calculateLevel1Index(Field(i)) + ), + statusStorage.getLevel1Witness( + statusStorage.calculateLevel1Index(Field(i)) + ), + configStorage.getLevel1Witness( + configStorage.calculateLevel1Index(Field(i)) + ) + ); + + // update storage: + ownerStorage.updateLeaf( + ownerStorage.calculateLeaf(campaignActions[i].owner), + Field(i) + ); + campaignInfoStorage.updateLeaf( + campaignInfoStorage.calculateLeaf(campaignActions[i].ipfsHash), + Field(i) + ); + statusStorage.updateLeaf( + statusStorage.calculateLeaf(StatusEnum.APPLICATION), + Field(i) + ); + configStorage.updateLeaf( + configStorage.calculateLeaf({ + committeeId: campaignActions[i].committeeId, + keyId: campaignActions[i].keyId, + }), + Field(i) + ); + } + + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + campaignContract.rollup(createCampaignProof); + } + ); + await proveAndSend(tx, [feePayerKey], Contract.CAMPAIGN, 'rollup'); + + if (isParticipation) await wait(); } - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - participationContract.rollup(joinCampaignProof); - } - ); - await proveAndSend(tx, [feePayerKey], Contract.CAMPAIGN, 'rollup'); + if (isParticipation) { + await fetchAllContract(contracts, [Contract.PARTICIPATION]); + console.log('Join campaign...'); + let numCampaign = 2; + let participationContract = contracts[Contract.PARTICIPATION] + .contract as ParticipationContract; + + Provable.log('Onchain: ', participationContract.indexTreeRoot.get()); + + let joinCampaignInput = [ + new JoinCampaignInput({ + campaignId: Field(1), + projectId: Field(1), + participationInfo: IPFSHash.fromString( + mockParticipationIpfs[0] + ), + indexWitness: indexStorage.getWitness( + indexStorage.calculateLevel1Index({ + campaignId: Field(1), + projectId: Field(1), + }) + ), + memberLv1Witness: memberStorage.getLevel1Witness(Field(1)), + memberLv2Witness: new Level2Witness( + EMPTY_LEVEL_2_TREE().getWitness(0n) + ), // temp value since contract hasn't check this + // memberLv2Witness: memberStorage.getLevel2Witness(Field(1), Field(0)), // Field 0 = owner + projectRef: participationAddressStorage.getZkAppRef( + ZkAppEnum.PROJECT, + contracts[Contract.PROJECT].contract.address + ), + }), + new JoinCampaignInput({ + campaignId: Field(1), + projectId: Field(2), + participationInfo: IPFSHash.fromString( + mockParticipationIpfs[0] + ), + indexWitness: indexStorage.getWitness( + indexStorage.calculateLevel1Index({ + campaignId: Field(1), + projectId: Field(2), + }) + ), + memberLv1Witness: memberStorage.getLevel1Witness(Field(2)), + memberLv2Witness: new Level2Witness( + EMPTY_LEVEL_2_TREE().getWitness(0n) + ), // fake value since contract hasn't check this + // memberLv2Witness: memberStorage.getLevel2Witness(Field(2), Field(0)), // Field 0 = owner + projectRef: participationAddressStorage.getZkAppRef( + ZkAppEnum.PROJECT, + contracts[Contract.PROJECT].contract.address + ), + }), + ]; + + for (let i = 0; i < numCampaign; i++) { + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + participationContract.joinCampaign(joinCampaignInput[i]); + } + ); + await proveAndSend( + tx, + [feePayerKey], + Contract.PARTICIPATION, + 'joinCampaign' + ); + + participationAction.push( + new ParticipationAction({ + campaignId: joinCampaignInput[i].campaignId, + projectId: joinCampaignInput[i].projectId, + participationInfo: joinCampaignInput[i].participationInfo, + curApplicationInfoHash: Field(0), + }) + ); + } - if (isFunding) await wait(); - } + await wait(); - if (isFunding) { - await fetchAllContract(contracts, [Contract.FUNDING]); + console.log('Reduce participation...'); - let acc1: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile('keys/acc1.json', 'utf8') - ); - let acc2: { privateKey: string; publicKey: string } = JSON.parse( - await fs.readFile('keys/acc2.json', 'utf8') - ); + let joinCampaignProof = await JoinCampaign.firstStep( + participationContract.indexTreeRoot.get(), + participationContract.infoTreeRoot.get(), + participationContract.counterTreeRoot.get(), + participationContract.lastRolledUpActionState.get() + ); - let investors: Key[] = [ - { - privateKey: PrivateKey.fromBase58(acc1.privateKey), - publicKey: PublicKey.fromBase58(acc1.publicKey), - }, - { - privateKey: PrivateKey.fromBase58(acc2.privateKey), - publicKey: PublicKey.fromBase58(acc2.publicKey), - }, - ]; - - // total fund 0.02 = 2e7 - let secretVectors: CustomScalarArray[] = [ - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(1e7)), - CustomScalar.fromScalar(Scalar.from(0n)), - CustomScalar.fromScalar(Scalar.from(1e7)), - CustomScalar.fromScalar(Scalar.from(0n)), - ]), - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(0n)), - CustomScalar.fromScalar(Scalar.from(0n)), - CustomScalar.fromScalar(Scalar.from(1e7)), - CustomScalar.fromScalar(Scalar.from(1e7)), - ]), - ]; - - let randomsVectors: CustomScalarArray[] = [ - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(100n)), - CustomScalar.fromScalar(Scalar.from(200n)), - CustomScalar.fromScalar(Scalar.from(300n)), - CustomScalar.fromScalar(Scalar.from(400n)), - ]), - new CustomScalarArray([ - CustomScalar.fromScalar(Scalar.from(500n)), - CustomScalar.fromScalar(Scalar.from(600n)), - CustomScalar.fromScalar(Scalar.from(700n)), - CustomScalar.fromScalar(Scalar.from(800n)), - ]), - ]; - - console.log('Funding...'); - - let fundingContract = contracts[Contract.FUNDING] - .contract as FundingContract; - - let fundingInput = [ - new FundingInput({ - campaignId: Field(1), - committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, - secretVector: secretVectors[0], - random: randomsVectors[0], - treasuryContract: getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.TREASURY, - contracts[Contract.TREASURY].contract.address - ), - }), - new FundingInput({ - campaignId: Field(1), - committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, - secretVector: secretVectors[1], - random: randomsVectors[1], - treasuryContract: getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.TREASURY, - contracts[Contract.TREASURY].contract.address - ), - }), - ]; + for (let i = 0; i < numCampaign; i++) { + console.log('Step', i); + + let witness = indexStorage.getLevel1Witness( + indexStorage.calculateLevel1Index({ + campaignId: participationAction[i].campaignId, + projectId: participationAction[i].projectId, + }) + ); + + let rootFormWitness = witness.calculateRoot(Field(i + 1)); + let rootFormWitnessBEF = witness.calculateRoot(Field(i)); + + joinCampaignProof = await JoinCampaign.joinCampaign( + joinCampaignProof, + participationAction[i], + indexStorage.getLevel1Witness( + indexStorage.calculateLevel1Index({ + campaignId: participationAction[i].campaignId, + projectId: participationAction[i].projectId, + }) + ), + participationInfoStorage.getLevel1Witness( + participationInfoStorage.calculateLevel1Index({ + campaignId: participationAction[i].campaignId, + projectId: participationAction[i].projectId, + }) + ), + Field(i), // current couter of each campaign is 0 + counterStorage.getLevel1Witness( + counterStorage.calculateLevel1Index( + participationAction[i].campaignId + ) + ) + ); + + // update storage: + indexStorage.updateLeaf( + indexStorage.calculateLeaf(Field(i + 1)), // index start from 1 + indexStorage.calculateLevel1Index({ + campaignId: participationAction[i].campaignId, + projectId: participationAction[i].projectId, + }) + ); + + Provable.log('index off aft: ', indexStorage.root); + + participationInfoStorage.updateLeaf( + participationInfoStorage.calculateLeaf( + participationAction[i].participationInfo + ), + participationInfoStorage.calculateLevel1Index({ + campaignId: participationAction[i].campaignId, + projectId: participationAction[i].projectId, + }) + ); + counterStorage.updateLeaf( + counterStorage.calculateLeaf(Field(i + 1)), + counterStorage.calculateLevel1Index( + participationAction[i].campaignId + ) + ); + } - let result: { - R: ZkApp.Request.RequestVector; - M: ZkApp.Request.RequestVector; - }; - let investorNonce = []; - for (let i = 0; i < investors.length; i++) { - let investor = await fetchAccount({ - publicKey: investors[i].publicKey, - }); - investorNonce.push(Number(investor.account?.nonce) - 1); + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + participationContract.rollup(joinCampaignProof); + } + ); + await proveAndSend(tx, [feePayerKey], Contract.CAMPAIGN, 'rollup'); + + if (isFunding) await wait(); } - for (let i = 0; i < investors.length; i++) { - let balanceBefore = Number(Account(investors[i].publicKey).balance.get()); - console.log('Balance before: ', balanceBefore); - tx = await Mina.transaction( - { sender: investors[i].publicKey, fee, nonce: ++investorNonce[i] }, - () => { - result = fundingContract.fund(fundingInput[i]); + if (isFunding) { + await fetchAllContract(contracts, [Contract.FUNDING]); + + let acc1: { privateKey: string; publicKey: string } = JSON.parse( + await fs.readFile('keys/acc1.json', 'utf8') + ); + let acc2: { privateKey: string; publicKey: string } = JSON.parse( + await fs.readFile('keys/acc2.json', 'utf8') + ); + + let investors: Key[] = [ + { + privateKey: PrivateKey.fromBase58(acc1.privateKey), + publicKey: PublicKey.fromBase58(acc1.publicKey), + }, + { + privateKey: PrivateKey.fromBase58(acc2.privateKey), + publicKey: PublicKey.fromBase58(acc2.publicKey), + }, + ]; + + // total fund 0.02 = 2e7 + let secretVectors: CustomScalarArray[] = [ + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(1e7)), + CustomScalar.fromScalar(Scalar.from(0n)), + CustomScalar.fromScalar(Scalar.from(1e7)), + CustomScalar.fromScalar(Scalar.from(0n)), + ]), + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(0n)), + CustomScalar.fromScalar(Scalar.from(0n)), + CustomScalar.fromScalar(Scalar.from(1e7)), + CustomScalar.fromScalar(Scalar.from(1e7)), + ]), + ]; + + let randomsVectors: CustomScalarArray[] = [ + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(100n)), + CustomScalar.fromScalar(Scalar.from(200n)), + CustomScalar.fromScalar(Scalar.from(300n)), + CustomScalar.fromScalar(Scalar.from(400n)), + ]), + new CustomScalarArray([ + CustomScalar.fromScalar(Scalar.from(500n)), + CustomScalar.fromScalar(Scalar.from(600n)), + CustomScalar.fromScalar(Scalar.from(700n)), + CustomScalar.fromScalar(Scalar.from(800n)), + ]), + ]; + + console.log('Funding...'); + + let fundingContract = contracts[Contract.FUNDING] + .contract as FundingContract; + + let fundingInput = [ + new FundingInput({ + campaignId: Field(1), + committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, + secretVector: secretVectors[0], + random: randomsVectors[0], + treasuryContract: fundingAddressStorage.getZkAppRef( + ZkAppEnum.TREASURY, + contracts[Contract.TREASURY].contract.address + ), + }), + new FundingInput({ + campaignId: Field(1), + committeePublicKey: contracts[Contract.COMMITTEE].key.publicKey, + secretVector: secretVectors[1], + random: randomsVectors[1], + treasuryContract: fundingAddressStorage.getZkAppRef( + ZkAppEnum.TREASURY, + contracts[Contract.TREASURY].contract.address + ), + }), + ]; + + let result: { + R: ZkApp.Request.RequestVector; + M: ZkApp.Request.RequestVector; + }; + let investorNonce = []; + for (let i = 0; i < investors.length; i++) { + let investor = await fetchAccount({ + publicKey: investors[i].publicKey, + }); + investorNonce.push(Number(investor.account?.nonce) - 1); } - ); - // await proveAndSend(tx, [investors[i]], Contract.FUNDING, 'fund'); - - let { R, M } = result!; - - fundingAction.push( - new FundingAction({ - campaignId: fundingInput[i].campaignId, - R, - M, - }) - ); - } - // await wait(); - await fetchAllContract(contracts, [Contract.FUNDING]); + for (let i = 0; i < investors.length; i++) { + let balanceBefore = Number( + Account(investors[i].publicKey).balance.get() + ); + console.log('Balance before: ', balanceBefore); + tx = await Mina.transaction( + { + sender: investors[i].publicKey, + fee, + nonce: ++investorNonce[i], + }, + () => { + result = fundingContract.fund(fundingInput[i]); + } + ); + // await proveAndSend(tx, [investors[i]], Contract.FUNDING, 'fund'); + + let { R, M } = result!; + + fundingAction.push( + new FundingAction({ + campaignId: fundingInput[i].campaignId, + R, + M, + }) + ); + } - let lastActionState = fundingContract.actionState.get(); - let fundingActionStates = contracts[Contract.FUNDING].actionStates; - let index = fundingActionStates.findIndex((obj) => - Boolean(obj.equals(lastActionState)) - ); + // await wait(); + await fetchAllContract(contracts, [Contract.FUNDING]); - console.log('Reduce funding...'); + let lastActionState = fundingContract.actionState.get(); + let fundingActionStates = contracts[Contract.FUNDING].actionStates; + let index = fundingActionStates.findIndex((obj) => + Boolean(obj.equals(lastActionState)) + ); - let reduceFundingProof = await CreateReduceProof.firstStep( - fundingContract.actionState.get(), - fundingContract.actionStatus.get() - ); + console.log('Reduce funding...'); - for (let i = 0; i < investors.length; i++) { - console.log('Step', i); - reduceFundingProof = await CreateReduceProof.nextStep( - reduceFundingProof, - fundingAction[i], - fundingReduceStorage.getWitness(fundingActionStates[index + 1 + i]) - ); - - // update storage: - fundingReduceStorage.updateLeaf( - fundingReduceStorage.calculateIndex(fundingActionStates[index + 1 + i]), - fundingReduceStorage.calculateLeaf(ActionStatus.REDUCED) - ); - } + let reduceFundingProof = await CreateReduceProof.firstStep( + fundingContract.actionState.get(), + fundingContract.actionStatus.get() + ); - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - fundingContract.reduce(reduceFundingProof); - } - ); - await proveAndSend(tx, [feePayerKey], Contract.FUNDING, 'reduce'); + for (let i = 0; i < investors.length; i++) { + console.log('Step', i); + reduceFundingProof = await CreateReduceProof.nextStep( + reduceFundingProof, + fundingAction[i], + fundingReduceStorage.getWitness( + fundingActionStates[index + 1 + i] + ) + ); + + // update storage: + fundingReduceStorage.updateLeaf( + fundingReduceStorage.calculateIndex( + fundingActionStates[index + 1 + i] + ), + fundingReduceStorage.calculateLeaf(ActionStatus.REDUCED) + ); + } - await wait(); - await fetchAllContract(contracts, [Contract.FUNDING, Contract.REQUEST]); + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + fundingContract.reduce(reduceFundingProof); + } + ); + await proveAndSend(tx, [feePayerKey], Contract.FUNDING, 'reduce'); - console.log('RollUp funding...'); + await wait(); + await fetchAllContract(contracts, [Contract.FUNDING, Contract.REQUEST]); - let rollUpFundingProof = await CreateRollupProof.firstStep( - fundingAction[0].campaignId, - secretVectors[0].length, - fundingContract.actionStatus.get() - ); + console.log('RollUp funding...'); - for (let i = 0; i < investors.length; i++) { - console.log('Step', i); - rollUpFundingProof = await CreateRollupProof.nextStep( - rollUpFundingProof, - fundingAction[i], - fundingActionStates[index + i], - fundingReduceStorage.getWitness(fundingActionStates[index + 1 + i]) - ); - - // update storage: - fundingReduceStorage.updateLeaf( - fundingReduceStorage.calculateIndex(fundingActionStates[index + 1 + i]), - fundingReduceStorage.calculateLeaf(ActionStatus.ROLL_UPED) - ); - } + let rollUpFundingProof = await CreateRollupProof.firstStep( + fundingAction[0].campaignId, + secretVectors[0].length, + fundingContract.actionStatus.get() + ); - tx = await Mina.transaction( - { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, - () => { - fundingContract.rollupRequest( - rollUpFundingProof, - Field(2), - Field(2), - sumRStorage.getLevel1Witness( - sumRStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - sumMStorage.getLevel1Witness( - sumMStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - requestIdStorage.getLevel1Witness( - requestIdStorage.calculateLevel1Index(fundingAction[0].campaignId) - ), - getZkAppRef( - fundingAddressStorage.addresses, - ZkAppEnum.REQUEST, - contracts[Contract.REQUEST].contract.address - ) + for (let i = 0; i < investors.length; i++) { + console.log('Step', i); + rollUpFundingProof = await CreateRollupProof.nextStep( + rollUpFundingProof, + fundingAction[i], + fundingActionStates[index + i], + fundingReduceStorage.getWitness( + fundingActionStates[index + 1 + i] + ) + ); + + // update storage: + fundingReduceStorage.updateLeaf( + fundingReduceStorage.calculateIndex( + fundingActionStates[index + 1 + i] + ), + fundingReduceStorage.calculateLeaf(ActionStatus.ROLL_UPED) + ); + } + + tx = await Mina.transaction( + { sender: feePayerKey.publicKey, fee, nonce: ++feePayerNonce }, + () => { + fundingContract.rollupRequest( + rollUpFundingProof, + Field(2), + Field(2), + sumRStorage.getLevel1Witness( + sumRStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + sumMStorage.getLevel1Witness( + sumMStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + requestIdStorage.getLevel1Witness( + requestIdStorage.calculateLevel1Index( + fundingAction[0].campaignId + ) + ), + fundingAddressStorage.getZkAppRef( + ZkAppEnum.REQUEST, + contracts[Contract.REQUEST].contract.address + ) + ); + } ); - } - ); - await proveAndSend(tx, [feePayerKey], Contract.FUNDING, ''); + await proveAndSend(tx, [feePayerKey], Contract.FUNDING, ''); - if (isTreasury) await wait(); - } + if (isTreasury) await wait(); + } - if (isTreasury) { - } + if (isTreasury) { + // + } - console.log('done all'); - console.timeEnd('runTime'); + console.log('done all'); + console.timeEnd('runTime'); } main(); diff --git a/src/scripts/Project.test.ts b/src/scripts/Project.test.ts index 06d7c86..889b0cf 100644 --- a/src/scripts/Project.test.ts +++ b/src/scripts/Project.test.ts @@ -5,135 +5,145 @@ import { Contract } from '../constants.js'; import { getProfiler } from './helper/profiler.js'; import { IPFSHash } from '@auxo-dev/auxo-libs'; import { - ProjectContract, - CreateProject, - CreateProjectInput, - ProjectProof, - ProjectAction, + ProjectContract, + CreateProject, + CreateProjectInput, + ProjectProof, + ProjectAction, } from '../contracts/Project.js'; import { - MemberArray, - MemberStorage, - InfoStorage, - AddressStorage, - EMPTY_LEVEL_2_TREE, + MemberArray, + MemberStorage, + InfoStorage, + AddressStorage, + EMPTY_LEVEL_2_TREE, } from '../contracts/ProjectStorage.js'; import { compile, deploy, proveAndSend } from '../libs/utils.js'; describe('Project', () => { - const doProofs = false; - const profiling = false; - const logMemory = false; - const cache = Cache.FileSystem('./caches'); - const ProjectProfiler = getProfiler('Benchmark Project'); - const profiler = profiling ? ProjectProfiler : undefined; - let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); - Mina.setActiveInstance(Local); + const doProofs = false; + const profiling = false; + const logMemory = false; + const cache = Cache.FileSystem('./caches'); + const ProjectProfiler = getProfiler('Benchmark Project'); + const profiler = profiling ? ProjectProfiler : undefined; + let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); + Mina.setActiveInstance(Local); - let accounts: Key[] = Local.testAccounts.slice(1, 5); - let feePayerKey: Key = accounts[0]; - let configJson: Config = JSON.parse(fs.readFileSync('config.json', 'utf8')); - let config = configJson.deployAliases[Contract.PROJECT]; - let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( - fs.readFileSync(config.keyPath, 'utf8') - ); - let project: any = { - key: { - privateKey: PrivateKey.fromBase58(keyBase58.privateKey), - publicKey: PublicKey.fromBase58(keyBase58.publicKey), - }, - actionStates: [Reducer.initialActionState], - }; - project.contract = new ProjectContract(project.key.publicKey); + let accounts: Key[] = Local.testAccounts.slice(1, 5); + let feePayerKey: Key = accounts[0]; + let configJson: Config = JSON.parse(fs.readFileSync('config.json', 'utf8')); + let config = configJson.deployAliases[Contract.PROJECT]; + let keyBase58: { privateKey: string; publicKey: string } = JSON.parse( + fs.readFileSync(config.keyPath, 'utf8') + ); + let project: any = { + key: { + privateKey: PrivateKey.fromBase58(keyBase58.privateKey), + publicKey: PublicKey.fromBase58(keyBase58.publicKey), + }, + actionStates: [Reducer.initialActionState], + }; + project.contract = new ProjectContract(project.key.publicKey); - // Project contract storage - let memberStorage = new MemberStorage(); - let infoStorage = new InfoStorage(); - let addressStorage = new AddressStorage(); + // Project contract storage + let memberStorage = new MemberStorage(); + let infoStorage = new InfoStorage(); + let addressStorage = new AddressStorage(); - // input - let arrayPublicKey = [ - accounts[1].publicKey, - accounts[2].publicKey, - accounts[3].publicKey, - ]; - let memberArray = new MemberArray(arrayPublicKey); - let createProjectInput = new CreateProjectInput({ - members: memberArray, - ipfsHash: IPFSHash.fromString('testing'), - payeeAccount: accounts[1].publicKey, - }); + // input + let arrayPublicKey = [ + accounts[1].publicKey, + accounts[2].publicKey, + accounts[3].publicKey, + ]; + let memberArray = new MemberArray(arrayPublicKey); + let createProjectInput = new CreateProjectInput({ + members: memberArray, + ipfsHash: IPFSHash.fromString('testing'), + payeeAccount: accounts[1].publicKey, + }); - // action - let action = new ProjectAction({ - projectId: Field(-1), - members: createProjectInput.members, - ipfsHash: createProjectInput.ipfsHash, - payeeAccount: createProjectInput.payeeAccount, - }); + // action + let action = new ProjectAction({ + projectId: Field(-1), + members: createProjectInput.members, + ipfsHash: createProjectInput.ipfsHash, + payeeAccount: createProjectInput.payeeAccount, + }); - it('should compile programs and contracts', async () => { - console.log('CreateProject.compile...'); - await compile(CreateProject, cache, logMemory, profiler); - if (doProofs) { - await compile(ProjectContract, cache, logMemory, profiler); - } else { - console.log('ProjectContract.analyzeMethods...'); - ProjectContract.analyzeMethods(); - } - }); + it('should compile programs and contracts', async () => { + console.log('CreateProject.compile...'); + await compile(CreateProject, cache, logMemory, profiler); + if (doProofs) { + await compile(ProjectContract, cache, logMemory, profiler); + } else { + console.log('ProjectContract.analyzeMethods...'); + ProjectContract.analyzeMethods(); + } + }); - it('should deploy contracts', async () => { - await deploy(project, [], feePayerKey); - }); + it('should deploy contracts', async () => { + await deploy(project, [], feePayerKey); + }); - it('should create a new project', async () => { - let tx = await Mina.transaction(feePayerKey.publicKey, () => { - project.contract.createProject(createProjectInput); + it('should create a new project', async () => { + let tx = await Mina.transaction(feePayerKey.publicKey, () => { + project.contract.createProject(createProjectInput); + }); + await proveAndSend( + tx, + [feePayerKey], + 'ProjectContract', + 'createProject' + ); }); - await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'createProject'); - }); - it('should update projects by reduce actions', async () => { - let reduceProof: ProjectProof = await CreateProject.firstStep( - project.contract.nextProjectId.get(), - project.contract.memberTreeRoot.get(), - project.contract.projectInfoTreeRoot.get(), - project.contract.payeeTreeRoot.get(), - project.contract.lastRolledUpActionState.get() - ); + it('should update projects by reduce actions', async () => { + let reduceProof: ProjectProof = await CreateProject.firstStep( + project.contract.nextProjectId.get(), + project.contract.memberTreeRoot.get(), + project.contract.projectInfoTreeRoot.get(), + project.contract.payeeTreeRoot.get(), + project.contract.lastRolledUpActionState.get() + ); - reduceProof = await CreateProject.nextStep( - reduceProof, - action, - memberStorage.getLevel1Witness( - memberStorage.calculateLevel1Index(Field(0)) - ), - infoStorage.getLevel1Witness(infoStorage.calculateLevel1Index(Field(0))), - addressStorage.getLevel1Witness( - addressStorage.calculateLevel1Index(Field(0)) - ) - ); + reduceProof = await CreateProject.nextStep( + reduceProof, + action, + memberStorage.getLevel1Witness( + memberStorage.calculateLevel1Index(Field(0)) + ), + infoStorage.getLevel1Witness( + infoStorage.calculateLevel1Index(Field(0)) + ), + addressStorage.getLevel1Witness( + addressStorage.calculateLevel1Index(Field(0)) + ) + ); - let tx = await Mina.transaction(feePayerKey.publicKey, () => { - project.contract.rollup(reduceProof); - }); - await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'rollup'); + let tx = await Mina.transaction(feePayerKey.publicKey, () => { + project.contract.rollup(reduceProof); + }); + await proveAndSend(tx, [feePayerKey], 'ProjectContract', 'rollup'); - let tree1 = EMPTY_LEVEL_2_TREE(); - for (let i = 0; i < Number(memberArray.length); i++) { - tree1.setLeaf(BigInt(i), MemberArray.hash(memberArray.get(Field(i)))); - } + let tree1 = EMPTY_LEVEL_2_TREE(); + for (let i = 0; i < Number(memberArray.length); i++) { + tree1.setLeaf( + BigInt(i), + MemberArray.hash(memberArray.get(Field(i))) + ); + } - // update storage: - memberStorage.updateInternal(Field(0), tree1); - infoStorage.updateLeaf( - infoStorage.calculateLeaf(createProjectInput.ipfsHash), - Field(0) - ); - addressStorage.updateLeaf( - addressStorage.calculateLeaf(createProjectInput.payeeAccount), - Field(0) - ); - }); + // update storage: + memberStorage.updateInternal(Field(0), tree1); + infoStorage.updateLeaf( + { level1Index: Field(0) }, + infoStorage.calculateLeaf(createProjectInput.ipfsHash) + ); + addressStorage.updateLeaf( + { level1Index: Field(0) }, + addressStorage.calculateLeaf(createProjectInput.payeeAccount) + ); + }); }); diff --git a/src/scripts/Treasury.test.ts b/src/scripts/Treasury.test.ts index 897a433..c69e4a4 100644 --- a/src/scripts/Treasury.test.ts +++ b/src/scripts/Treasury.test.ts @@ -194,7 +194,7 @@ // it('Deploy and funding', async () => { // await deploy( // contracts[Contract.TREASURY], -// [['zkApps', allAddressStorage.addresses.getRoot()]], +// [['zkApps', allAddressStorage.root]], // feePayerKey // ); diff --git a/src/scripts/helper/config.ts b/src/scripts/helper/config.ts index 023d427..3ca8bf7 100644 --- a/src/scripts/helper/config.ts +++ b/src/scripts/helper/config.ts @@ -2,25 +2,25 @@ import { Field, PrivateKey, PublicKey, SmartContract } from 'o1js'; // parse config and private key from file export type Config = { - deployAliases: Record< - string, - { - url: string; - keyPath: string; - fee: string; - feepayerKeyPath: string; - feepayerAlias: string; - } - >; + deployAliases: Record< + string, + { + url: string; + keyPath: string; + fee: string; + feepayerKeyPath: string; + feepayerAlias: string; + } + >; }; export type Key = { - privateKey: PrivateKey; - publicKey: PublicKey; + privateKey: PrivateKey; + publicKey: PublicKey; }; export type Contract = { - key: Key; - contract: SmartContract; - actionStates: Field[]; + key: Key; + contract: SmartContract; + actionStates: Field[]; }; diff --git a/src/scripts/helper/profiler.ts b/src/scripts/helper/profiler.ts index 0ab07bd..83195c9 100644 --- a/src/scripts/helper/profiler.ts +++ b/src/scripts/helper/profiler.ts @@ -6,50 +6,50 @@ export { getProfiler, Profiler }; const round = (x: number) => Math.round(x * 100) / 100; type Profiler = { - times: Record; - start: (lable: string) => void; - stop: () => Profiler; - store: () => void; + times: Record; + start: (lable: string) => void; + stop: () => Profiler; + store: () => void; }; function getProfiler(name: string): Profiler { - let times: Record = {}; - let label: string; - - return { - get times() { - return times; - }, - start(label_: string) { - label = label_; - times = { - ...times, - [label]: { - start: performance.now(), + let times: Record = {}; + let label: string; + + return { + get times() { + return times; + }, + start(label_: string) { + label = label_; + times = { + ...times, + [label]: { + start: performance.now(), + }, + }; + }, + stop() { + times[label].end = performance.now(); + return this; + }, + store() { + let profilingData = `## Times for ${name}\n\n`; + profilingData += `| Name | time passed in s |\n|---|---|`; + let totalTimePassed = 0; + + Object.keys(times).forEach((k) => { + let timePassed = (times[k].end - times[k].start) / 1000; + totalTimePassed += timePassed; + + profilingData += `\n|${k}|${round(timePassed)}|`; + }); + + profilingData += `\n\nIn total, it took ${round( + totalTimePassed + )} seconds to run the entire benchmark\n\n\n`; + + fs.appendFileSync('profiling.md', profilingData); }, - }; - }, - stop() { - times[label].end = performance.now(); - return this; - }, - store() { - let profilingData = `## Times for ${name}\n\n`; - profilingData += `| Name | time passed in s |\n|---|---|`; - let totalTimePassed = 0; - - Object.keys(times).forEach((k) => { - let timePassed = (times[k].end - times[k].start) / 1000; - totalTimePassed += timePassed; - - profilingData += `\n|${k}|${round(timePassed)}|`; - }); - - profilingData += `\n\nIn total, it took ${round( - totalTimePassed - )} seconds to run the entire benchmark\n\n\n`; - - fs.appendFileSync('profiling.md', profilingData); - }, - }; + }; } diff --git a/src/scripts/helper/randomAccounts.ts b/src/scripts/helper/randomAccounts.ts index a4f4b68..182935c 100644 --- a/src/scripts/helper/randomAccounts.ts +++ b/src/scripts/helper/randomAccounts.ts @@ -1,16 +1,16 @@ import { PrivateKey, PublicKey } from 'o1js'; export default function randomAccounts( - ...names: [K, ...K[]] + ...names: [K, ...K[]] ): { keys: Record; addresses: Record } { - let base58Keys = Array(names.length) - .fill('') - .map(() => PrivateKey.random().toBase58()); - let keys = Object.fromEntries( - names.map((name, idx) => [name, PrivateKey.fromBase58(base58Keys[idx])]) - ) as Record; - let addresses = Object.fromEntries( - names.map((name) => [name, keys[name].toPublicKey()]) - ) as Record; - return { keys, addresses }; + let base58Keys = Array(names.length) + .fill('') + .map(() => PrivateKey.random().toBase58()); + let keys = Object.fromEntries( + names.map((name, idx) => [name, PrivateKey.fromBase58(base58Keys[idx])]) + ) as Record; + let addresses = Object.fromEntries( + names.map((name) => [name, keys[name].toPublicKey()]) + ) as Record; + return { keys, addresses }; } diff --git a/src/scripts/mock/campaigns.json b/src/scripts/mock/campaigns.json index beb5d1e..97727d7 100644 --- a/src/scripts/mock/campaigns.json +++ b/src/scripts/mock/campaigns.json @@ -1,40 +1,40 @@ [ - { - "name": "Test Campaign 1", - "img": "", - "description": "Amet mollit ipsum in tempor. Culpa aute occaecat eiusmod aute id Lorem. Ullamco occaecat quis anim ad consequat duis sint culpa. Ipsum qui veniam anim mollit dolore deserunt est officia. Voluptate occaecat velit voluptate aute duis nulla dolor commodo. Ea enim culpa magna officia qui Lorem qui non.", - "timeline": { - "application": { - "from": "1704541265342", - "to": "1704541285342" - }, - "investment": { - "from": "1704541285342", - "to": "1704541305342" - }, - "allocation": { - "from": "1704541305342", - "to": "1704541325342" - } - }, - "privacyOption": { - "isPrivate": true, - "committeeId": 0, - "keyId": 0 - }, - "capacity": 4, - "fundingOption": 0, - "questions": [ - { - "question": "What are your visions?", - "hint": "Sit non minim cupidatat nostrud ipsum pariatur. Et Lorem et nulla nostrud ipsum fugiat irure non in qui laborum culpa ea exercitation. Excepteur aute fugiat ex dolor exercitation aliquip laboris quis irure labore mollit sit.", - "isRequired": true - }, - { - "question": "What is your roadmap?", - "hint": "Sit non minim cupidatat nostrud ipsum pariatur. Et Lorem et nulla nostrud ipsum fugiat irure non in qui laborum culpa ea exercitation. Excepteur aute fugiat ex dolor exercitation aliquip laboris quis irure labore mollit sit.", - "isRequired": false - } - ] - } + { + "name": "Test Campaign 1", + "img": "", + "description": "Amet mollit ipsum in tempor. Culpa aute occaecat eiusmod aute id Lorem. Ullamco occaecat quis anim ad consequat duis sint culpa. Ipsum qui veniam anim mollit dolore deserunt est officia. Voluptate occaecat velit voluptate aute duis nulla dolor commodo. Ea enim culpa magna officia qui Lorem qui non.", + "timeline": { + "application": { + "from": "1704541265342", + "to": "1704541285342" + }, + "investment": { + "from": "1704541285342", + "to": "1704541305342" + }, + "allocation": { + "from": "1704541305342", + "to": "1704541325342" + } + }, + "privacyOption": { + "isPrivate": true, + "committeeId": 0, + "keyId": 0 + }, + "capacity": 4, + "fundingOption": 0, + "questions": [ + { + "question": "What are your visions?", + "hint": "Sit non minim cupidatat nostrud ipsum pariatur. Et Lorem et nulla nostrud ipsum fugiat irure non in qui laborum culpa ea exercitation. Excepteur aute fugiat ex dolor exercitation aliquip laboris quis irure labore mollit sit.", + "isRequired": true + }, + { + "question": "What is your roadmap?", + "hint": "Sit non minim cupidatat nostrud ipsum pariatur. Et Lorem et nulla nostrud ipsum fugiat irure non in qui laborum culpa ea exercitation. Excepteur aute fugiat ex dolor exercitation aliquip laboris quis irure labore mollit sit.", + "isRequired": false + } + ] + } ] diff --git a/src/scripts/mock/participations.json b/src/scripts/mock/participations.json index 12a81fa..03d9882 100644 --- a/src/scripts/mock/participations.json +++ b/src/scripts/mock/participations.json @@ -1,22 +1,22 @@ [ - { - "answers": [ - "Dolore mollit commodo tempor ea reprehenderit proident in voluptate esse exercitation eu adipisicing Lorem. Eu officia tempor ut ex ex mollit exercitation minim laborum aute id ipsum eu est. Lorem laboris in cupidatat eiusmod ex anim consequat nisi aliqua aliquip laboris esse. Anim ut ex tempor officia pariatur.", - "Esse officia cupidatat est reprehenderit officia aute veniam nostrud aliquip voluptate. Adipisicing enim enim consequat labore nulla culpa officia cillum consectetur eu ullamco culpa minim exercitation. Nulla ullamco incididunt dolore enim consectetur quis ut laboris commodo consectetur. Et aliquip cillum non Lorem magna culpa. Reprehenderit pariatur dolor tempor incididunt laboris eu. Ullamco non dolore Lorem reprehenderit nulla reprehenderit dolor ipsum eu. Sint laboris duis Lorem esse velit." - ], - "scopeOfWorks": [ - { - "information": "Nisi consequat proident proident occaecat eu. Ex amet esse ex laboris tempor laborum cillum sint deserunt sit. Dolore magna exercitation nulla excepteur qui nostrud et enim laboris amet deserunt amet consectetur.", - "milestone": "Adipisicing sunt eu sint nisi elit proident excepteur. Labore anim in ullamco tempor dolor ullamco magna. Enim proident duis nisi incididunt nisi sunt irure id. Deserunt consectetur cupidatat pariatur ex pariatur velit consectetur mollit dolor esse id occaecat occaecat. Duis ullamco fugiat excepteur occaecat anim nisi ea pariatur.", - "raisingAmount": 10000, - "deadline": "1704559485856" - }, - { - "information": "Minim elit aute excepteur adipisicing occaecat dolore consectetur proident adipisicing amet incididunt laborum. Et ex sit dolor consequat ullamco elit pariatur ea Lorem. Lorem in mollit deserunt pariatur aute eu ad velit sint et nulla eu adipisicing. Irure reprehenderit amet dolor laboris eiusmod excepteur ad qui do veniam occaecat. Duis ipsum deserunt nostrud id est dolore commodo do veniam do.", - "milestone": "Elit ex eu sint culpa dolor aute ex mollit. Quis dolor mollit enim enim culpa cillum irure aute exercitation. Proident laboris enim commodo laborum id quis elit aute deserunt duis ut mollit ipsum. Laborum quis mollit irure laboris cupidatat pariatur labore duis exercitation nulla. Non sint elit ad ea ipsum non commodo deserunt cillum ea non sint reprehenderit. Quis duis dolore cillum et.", - "raisingAmount": 5000 - } - ], - "documents": [] - } + { + "answers": [ + "Dolore mollit commodo tempor ea reprehenderit proident in voluptate esse exercitation eu adipisicing Lorem. Eu officia tempor ut ex ex mollit exercitation minim laborum aute id ipsum eu est. Lorem laboris in cupidatat eiusmod ex anim consequat nisi aliqua aliquip laboris esse. Anim ut ex tempor officia pariatur.", + "Esse officia cupidatat est reprehenderit officia aute veniam nostrud aliquip voluptate. Adipisicing enim enim consequat labore nulla culpa officia cillum consectetur eu ullamco culpa minim exercitation. Nulla ullamco incididunt dolore enim consectetur quis ut laboris commodo consectetur. Et aliquip cillum non Lorem magna culpa. Reprehenderit pariatur dolor tempor incididunt laboris eu. Ullamco non dolore Lorem reprehenderit nulla reprehenderit dolor ipsum eu. Sint laboris duis Lorem esse velit." + ], + "scopeOfWorks": [ + { + "information": "Nisi consequat proident proident occaecat eu. Ex amet esse ex laboris tempor laborum cillum sint deserunt sit. Dolore magna exercitation nulla excepteur qui nostrud et enim laboris amet deserunt amet consectetur.", + "milestone": "Adipisicing sunt eu sint nisi elit proident excepteur. Labore anim in ullamco tempor dolor ullamco magna. Enim proident duis nisi incididunt nisi sunt irure id. Deserunt consectetur cupidatat pariatur ex pariatur velit consectetur mollit dolor esse id occaecat occaecat. Duis ullamco fugiat excepteur occaecat anim nisi ea pariatur.", + "raisingAmount": 10000, + "deadline": "1704559485856" + }, + { + "information": "Minim elit aute excepteur adipisicing occaecat dolore consectetur proident adipisicing amet incididunt laborum. Et ex sit dolor consequat ullamco elit pariatur ea Lorem. Lorem in mollit deserunt pariatur aute eu ad velit sint et nulla eu adipisicing. Irure reprehenderit amet dolor laboris eiusmod excepteur ad qui do veniam occaecat. Duis ipsum deserunt nostrud id est dolore commodo do veniam do.", + "milestone": "Elit ex eu sint culpa dolor aute ex mollit. Quis dolor mollit enim enim culpa cillum irure aute exercitation. Proident laboris enim commodo laborum id quis elit aute deserunt duis ut mollit ipsum. Laborum quis mollit irure laboris cupidatat pariatur labore duis exercitation nulla. Non sint elit ad ea ipsum non commodo deserunt cillum ea non sint reprehenderit. Quis duis dolore cillum et.", + "raisingAmount": 5000 + } + ], + "documents": [] + } ] diff --git a/src/scripts/mock/projects.json b/src/scripts/mock/projects.json index 55b06b2..4b31424 100644 --- a/src/scripts/mock/projects.json +++ b/src/scripts/mock/projects.json @@ -1,33 +1,33 @@ [ - { - "name": "Test Project 1", - "publicKey": "B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S", - "description": "Est velit tempor exercitation nostrud commodo esse. Voluptate eu dolore esse ea ipsum occaecat ea consectetur do elit quis ad sit sunt. Qui eiusmod nulla ut culpa. Nulla laboris consequat sit aute adipisicing excepteur ea dolore cupidatat voluptate dolor enim anim commodo. Proident elit qui consequat culpa do. Reprehenderit id nulla cupidatat consectetur deserunt nostrud. Aliquip proident eiusmod occaecat exercitation laboris veniam nulla cillum ullamco excepteur voluptate culpa tempor.", - "problemStatement": "Ipsum ea minim aliqua do sint exercitation. Commodo nostrud commodo cupidatat et occaecat ex. Est Lorem ullamco laboris reprehenderit Lorem voluptate proident excepteur non laborum. Aliquip exercitation tempor irure veniam voluptate magna. Commodo excepteur ut amet ad reprehenderit ea voluptate enim aliqua do sit excepteur amet. Adipisicing nostrud cillum magna sunt laborum proident est ex excepteur reprehenderit excepteur.", - "solution": "Nostrud fugiat deserunt duis exercitation ex deserunt dolor officia non veniam laboris aliquip non qui. Id amet sit ullamco minim do est labore reprehenderit Lorem. Irure quis ex in dolore reprehenderit consequat sit est ipsum non ex eu do.", - "challengesAndRisks": "Ipsum voluptate sit officia irure ex irure nostrud duis. Tempor voluptate et aliquip non dolor ex esse id qui occaecat ut. Quis ipsum ut tempor do consequat labore deserunt dolore eu qui consectetur. Ad qui nulla officia ipsum pariatur sit ipsum velit.", - "members": [ - { - "name": "Nguyen", - "role": "ZkApp Developer", - "link": "https://nguyen.ph" - }, - { - "name": "Minh", - "role": "ZkApp Developer", - "link": "https://minh.nh" - }, - { - "name": "Nam", - "role": "Backend Developer", - "link": "https://nam.pv" - }, - { - "name": "Quyen", - "role": "Frontend Developer", - "link": "https://quyen.ld" - } - ], - "documents": [] - } + { + "name": "Test Project 1", + "publicKey": "B62qnk1is4cK94PCX1QTwPM1SxfeCF9CcN6Nr7Eww3JLDgvxfWdhR5S", + "description": "Est velit tempor exercitation nostrud commodo esse. Voluptate eu dolore esse ea ipsum occaecat ea consectetur do elit quis ad sit sunt. Qui eiusmod nulla ut culpa. Nulla laboris consequat sit aute adipisicing excepteur ea dolore cupidatat voluptate dolor enim anim commodo. Proident elit qui consequat culpa do. Reprehenderit id nulla cupidatat consectetur deserunt nostrud. Aliquip proident eiusmod occaecat exercitation laboris veniam nulla cillum ullamco excepteur voluptate culpa tempor.", + "problemStatement": "Ipsum ea minim aliqua do sint exercitation. Commodo nostrud commodo cupidatat et occaecat ex. Est Lorem ullamco laboris reprehenderit Lorem voluptate proident excepteur non laborum. Aliquip exercitation tempor irure veniam voluptate magna. Commodo excepteur ut amet ad reprehenderit ea voluptate enim aliqua do sit excepteur amet. Adipisicing nostrud cillum magna sunt laborum proident est ex excepteur reprehenderit excepteur.", + "solution": "Nostrud fugiat deserunt duis exercitation ex deserunt dolor officia non veniam laboris aliquip non qui. Id amet sit ullamco minim do est labore reprehenderit Lorem. Irure quis ex in dolore reprehenderit consequat sit est ipsum non ex eu do.", + "challengesAndRisks": "Ipsum voluptate sit officia irure ex irure nostrud duis. Tempor voluptate et aliquip non dolor ex esse id qui occaecat ut. Quis ipsum ut tempor do consequat labore deserunt dolore eu qui consectetur. Ad qui nulla officia ipsum pariatur sit ipsum velit.", + "members": [ + { + "name": "Nguyen", + "role": "ZkApp Developer", + "link": "https://nguyen.ph" + }, + { + "name": "Minh", + "role": "ZkApp Developer", + "link": "https://minh.nh" + }, + { + "name": "Nam", + "role": "Backend Developer", + "link": "https://nam.pv" + }, + { + "name": "Quyen", + "role": "Frontend Developer", + "link": "https://quyen.ld" + } + ], + "documents": [] + } ] diff --git a/src/scripts/updatePrivateKey.ts b/src/scripts/updatePrivateKey.ts index 9f7e5d0..34c5352 100644 --- a/src/scripts/updatePrivateKey.ts +++ b/src/scripts/updatePrivateKey.ts @@ -2,8 +2,8 @@ import fs from 'fs'; import { PrivateKey, PublicKey } from 'o1js'; interface key { - privateKey: string; - publicKey: string; + privateKey: string; + publicKey: string; } // Define the data for each file @@ -11,104 +11,105 @@ let filesData: key[] = []; // Define the file names as an array const fileNames = [ - 'committee.json', - 'dkg.json', - 'round1.json', - 'round2.json', - 'response.json', - 'request.json', - 'project.json', - 'campaign.json', - 'participation.json', - 'funding.json', - 'treasury.json', + 'committee.json', + 'dkg.json', + 'round1.json', + 'round2.json', + 'response.json', + 'request.json', + 'project.json', + 'campaign.json', + 'participation.json', + 'funding.json', + 'treasury.json', ]; function createFileData() { - let tempFilesData: key[] = []; - for (let i = 0; i < fileNames.length; i++) { - let sk: PrivateKey = PrivateKey.random(); - let pk: PublicKey = sk.toPublicKey(); - tempFilesData.push({ - privateKey: sk.toBase58(), - publicKey: pk.toBase58(), - }); - } - filesData = tempFilesData; + let tempFilesData: key[] = []; + for (let i = 0; i < fileNames.length; i++) { + let sk: PrivateKey = PrivateKey.random(); + let pk: PublicKey = sk.toPublicKey(); + tempFilesData.push({ + privateKey: sk.toBase58(), + publicKey: pk.toBase58(), + }); + } + filesData = tempFilesData; } // Function to write the files function writeFiles() { - filesData.forEach((data, index) => { - const path = './keys/'; - let fileName = fileNames[index]; - fileName = path + fileName; - const fileContent = JSON.stringify(data, null, 2); - - fs.writeFile(fileName, fileContent, (err) => { - if (err) { - console.error(`Error writing ${fileName}:`, err); - } else { - console.log(`${fileName} created successfully.`); - } + filesData.forEach((data, index) => { + const path = './keys/'; + let fileName = fileNames[index]; + fileName = path + fileName; + const fileContent = JSON.stringify(data, null, 2); + + fs.writeFile(fileName, fileContent, (err) => { + if (err) { + console.error(`Error writing ${fileName}:`, err); + } else { + console.log(`${fileName} created successfully.`); + } + }); }); - }); } interface Key { - privateKey: string; - publicKey: string; + privateKey: string; + publicKey: string; } interface DeployAlias { - url: string; - keyPath: string; - feepayerKeyPath: string; - feepayerAlias: string; - fee: string; + url: string; + keyPath: string; + feepayerKeyPath: string; + feepayerAlias: string; + fee: string; } interface DeployAliases { - version: number; - deployAliases: { - [alias: string]: DeployAlias; - }; + version: number; + deployAliases: { + [alias: string]: DeployAlias; + }; } // Existing code... function createDeployAliases(): DeployAliases { - const deployAliases: DeployAliases = { - version: 1, - deployAliases: {}, - }; - - fileNames.forEach((fileName) => { - const alias = fileName.split('.')[0]; // Get the alias name from the file name - deployAliases.deployAliases[alias] = { - url: 'https://proxy.berkeley.minaexplorer.com/graphql', - keyPath: `keys/${fileName}`, - feepayerKeyPath: '/home/huyminh/.cache/zkapp-cli/keys/myaccount.json', - feepayerAlias: 'myaccount', - fee: '0.1', + const deployAliases: DeployAliases = { + version: 1, + deployAliases: {}, }; - }); - return deployAliases; + fileNames.forEach((fileName) => { + const alias = fileName.split('.')[0]; // Get the alias name from the file name + deployAliases.deployAliases[alias] = { + url: 'https://proxy.berkeley.minaexplorer.com/graphql', + keyPath: `keys/${fileName}`, + feepayerKeyPath: + '/home/huyminh/.cache/zkapp-cli/keys/myaccount.json', + feepayerAlias: 'myaccount', + fee: '0.1', + }; + }); + + return deployAliases; } function writeDeployAliasesFile() { - const deployAliases = createDeployAliases(); - const fileName = './config.json'; // Specify the new file name - const fileContent = JSON.stringify(deployAliases, null, 2); - - fs.writeFile(fileName, fileContent, (err) => { - if (err) { - console.error(`Error writing ${fileName}:`, err); - } else { - console.log(`${fileName} created successfully.`); - } - }); + const deployAliases = createDeployAliases(); + const fileName = './config.json'; // Specify the new file name + const fileContent = JSON.stringify(deployAliases, null, 2); + + fs.writeFile(fileName, fileContent, (err) => { + if (err) { + console.error(`Error writing ${fileName}:`, err); + } else { + console.log(`${fileName} created successfully.`); + } + }); } // Rest of the code... diff --git a/tsconfig.noscripts.json b/tsconfig.noscripts.json new file mode 100644 index 0000000..12dc5c7 --- /dev/null +++ b/tsconfig.noscripts.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["./src/scripts"] +}