├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── README.md ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js ├── src ├── api │ ├── compile.ts │ ├── deposit_bounty.ts │ ├── exec.ts │ ├── index.ts │ ├── prove.ts │ ├── prove_inputgen.ts │ ├── prove_mock.ts │ ├── publish.ts │ ├── setup.ts │ ├── task_dispatcher.ts │ ├── trigger.ts │ ├── upload.ts │ └── verify.ts ├── common │ ├── abi │ │ └── clecontractabi.ts │ ├── api_helper.ts │ ├── compatible.ts │ ├── constants.ts │ ├── error.ts │ ├── ethers_helper.ts │ ├── event.ts │ ├── index.ts │ ├── log_utils.ts │ ├── logger.ts │ ├── rlp.ts │ ├── tx_receipt.ts │ └── utils.ts ├── ddp │ ├── ethereum │ │ └── index.ts │ ├── hub.ts │ └── interface.ts ├── dsp │ ├── ethereum-offchain.bytes │ │ └── index.ts │ ├── ethereum.unsafe-ethereum │ │ ├── dataprep.ts │ │ └── index.ts │ ├── ethereum.unsafe │ │ ├── dataprep.ts │ │ ├── fill.ts │ │ ├── index.ts │ │ └── prepare.ts │ ├── ethereum │ │ ├── aux.ts │ │ ├── blockprep.ts │ │ ├── fill_blocks.ts │ │ ├── index.ts │ │ ├── mpt_input.ts │ │ └── prepare_blocks.ts │ ├── hooks.ts │ ├── hub.ts │ ├── index.ts │ ├── interface.ts │ └── types.ts ├── index.ts ├── requests │ ├── error_handle.ts │ ├── index.ts │ ├── ora_prove.ts │ ├── ora_setup.ts │ ├── pinata_upload.ts │ ├── url.ts │ ├── zkwasm_deploy.ts │ ├── zkwasm_imagedetails.ts │ ├── zkwasm_imagetask.ts │ ├── zkwasm_prove.ts │ ├── zkwasm_setup.ts │ └── zkwasm_taskdetails.ts ├── types │ ├── api.ts │ └── index.ts └── yaml │ ├── cleyaml.ts │ ├── cleyaml_eth.ts │ ├── cleyaml_off.ts │ ├── index.ts │ └── interface.ts ├── tests ├── compile.test.ts ├── compile_test_impl.ts ├── config-example.ts ├── deposit.test.ts ├── dispatch.test.ts ├── dsp-hub.test.ts ├── dsp.test.ts ├── exec.test.ts ├── exec_test_impl.ts ├── fixtures │ ├── compile │ │ ├── cle.yaml │ │ └── mapping.ts │ └── dsp │ │ ├── ethereum(event) │ │ ├── cle.yaml │ │ └── mapping.ts │ │ ├── ethereum(storage) │ │ ├── cle.yaml │ │ └── mapping.ts │ │ ├── ethereum.unsafe-ethereum │ │ ├── cle.yaml │ │ └── mapping.ts │ │ └── ethereum.unsafe │ │ ├── cle.yaml │ │ └── mapping.ts ├── fixureoptions.ts ├── prove.test.ts ├── publish.test.ts ├── rlp.test.ts ├── setup.test.ts ├── test_zkwasm_setup-usinghelper.js ├── trigger.test.ts ├── utils.test.ts ├── utils │ ├── ethers.ts │ ├── file.ts │ └── yaml.ts └── verify.test.ts ├── tsconfig.json └── types └── global.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | tests/build 2 | tests/fixtures/**/* 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@murongg", 3 | "rules": { 4 | "no-console": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Install pnpm 20 | uses: pnpm/action-setup@v2 21 | 22 | - name: Set node 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: 18.x 26 | 27 | - run: npx changelogithub 28 | env: 29 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | 4 | .idea/ 5 | yarn.lock 6 | tests/config.js 7 | tests/config.ts 8 | dist 9 | .DS_Store 10 | 11 | tests/fixtures/compile/build/* 12 | tests/fixtures/build 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [@ora-io/cle-api](https://www.npmjs.com/package/@ora-io/cle-api) 2 | 3 | ## Install 4 | 5 | ```bash 6 | npm i @ora-io/cle-api 7 | ``` 8 | 9 | ## Intro 10 | 11 | This is the API lib for ORA CLE interactions. 12 | 13 | ## Dev 14 | Notice: remember to update 'files' in package.json when adding new folders / top level files 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ora-io/cle-api", 3 | "type": "module", 4 | "version": "0.1.5", 5 | "packageManager": "pnpm@8.6.0", 6 | "description": "API for interacting with ORA CLEs", 7 | "author": "Norman (nom4dv3) & Suning (msfew)", 8 | "license": "ISC", 9 | "homepage": "https://github.com/ora-io/cle-api#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ora-io/cle-api.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/ora-io/cle-api/issues" 16 | }, 17 | "keywords": [ 18 | "cle", 19 | "api", 20 | "ora" 21 | ], 22 | "sideEffects": false, 23 | "exports": { 24 | ".": { 25 | "types": "./dist/index.d.ts", 26 | "require": "./dist/index.cjs", 27 | "import": "./dist/index.mjs" 28 | }, 29 | "./browser": { 30 | "types": "./dist/index.browser.d.ts", 31 | "require": "./dist/index.browser.cjs", 32 | "import": "./dist/index.browser.mjs" 33 | } 34 | }, 35 | "main": "./dist/index.mjs", 36 | "module": "./dist/index.mjs", 37 | "types": "./dist/index.d.ts", 38 | "typesVersions": { 39 | "*": { 40 | "*": [ 41 | "./dist/*", 42 | "./dist/index.d.ts" 43 | ], 44 | "browser": [ 45 | "./dist/index.browser.d.ts" 46 | ] 47 | } 48 | }, 49 | "files": [ 50 | "dist" 51 | ], 52 | "scripts": { 53 | "build": "rollup -c --bundleConfigAsCjs", 54 | "lint": "eslint .", 55 | "lint:fix": "eslint . --fix", 56 | "prepublishOnly": "nr build", 57 | "release": "cle-scripts release && cle-scripts publish", 58 | "start": "esno src/index.ts", 59 | "test": "vitest", 60 | "typecheck": "tsc --noEmit", 61 | "testexec": "npm test exec", 62 | "testprove": "npm test prove", 63 | "testsetup": "npm test setup", 64 | "testverify": "npm test verify" 65 | }, 66 | "dependencies": { 67 | "@ethereumjs/rlp": "^5.0.1", 68 | "@murongg/utils": "^0.1.20", 69 | "@ora-io/zkwasm-service-helper": "^1.0.2", 70 | "assemblyscript": "^0.27.22", 71 | "axios": "^1.6.2", 72 | "axios-retry": "^3.9.1", 73 | "bn.js": "^5.2.1", 74 | "ethers": "^5.7.2", 75 | "form-data": "^4.0.0", 76 | "js-yaml": "^4.1.0", 77 | "semver": "^7.5.4", 78 | "web3-eth-contract": "^1.8.2", 79 | "zkwasm-toolchain": "^0.0.7" 80 | }, 81 | "devDependencies": { 82 | "@murongg/eslint-config": "^0.2.1", 83 | "@ora-io/cle-lib": "^0.1.3", 84 | "@ora-io/release-scripts": "^0.0.1", 85 | "@rollup/plugin-babel": "^6.0.3", 86 | "@rollup/plugin-commonjs": "^25.0.4", 87 | "@rollup/plugin-json": "^6.0.0", 88 | "@rollup/plugin-node-resolve": "^15.2.1", 89 | "@types/bn.js": "^5.1.5", 90 | "@types/js-yaml": "^4.0.9", 91 | "@types/node": "^20.10.0", 92 | "@types/semver": "^7.5.6", 93 | "bumpp": "^9.2.1", 94 | "eslint": "^8.54.0", 95 | "esno": "^4.0.0", 96 | "lint-staged": "^15.1.0", 97 | "rimraf": "^5.0.5", 98 | "rollup": "^3.29.3", 99 | "rollup-plugin-dts": "^6.0.2", 100 | "rollup-plugin-esbuild": "^6.0.1", 101 | "simple-git-hooks": "^2.9.0", 102 | "ts-node": "^10.9.1", 103 | "typescript": "^5.3.2", 104 | "unbuild": "^2.0.0", 105 | "vite": "^5.0.2", 106 | "vitest": "^0.34.6" 107 | }, 108 | "simple-git-hooks": { 109 | "pre-commit": "pnpm lint-staged" 110 | }, 111 | "lint-staged": { 112 | "*": "eslint --fix" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { builtinModules } from 'node:module' 2 | import path from 'node:path' 3 | import esbuild from 'rollup-plugin-esbuild' 4 | import { dts } from 'rollup-plugin-dts' 5 | // import nodeResolve from '@rollup/plugin-node-resolve' 6 | import commonjs from '@rollup/plugin-commonjs' 7 | import json from '@rollup/plugin-json' 8 | import { defineConfig } from 'rollup' 9 | 10 | const input = path.join(__dirname, './src/index.ts') 11 | 12 | const external = [ 13 | ...builtinModules, 14 | 'ethers', 15 | 'bn.js', 16 | 'js-yaml', 17 | '@ora-io/zkwasm-service-helper', 18 | 'web3-eth-contract', 19 | 'semver', 20 | 'axios', 21 | 'form-data', 22 | '@ethereumjs/rlp', 23 | ] 24 | 25 | const plugins = [ 26 | json(), 27 | // nodeResolve({ 28 | // preferBuiltins: false, 29 | // bowser: true 30 | // }), 31 | commonjs(), 32 | ] 33 | 34 | const nodePlugins = [ 35 | ...plugins, 36 | esbuild.default({ 37 | target: 'node14', 38 | define: { 39 | __BROWSER__: 'false', 40 | }, 41 | }), 42 | ] 43 | 44 | const browserPlugins = [ 45 | ...plugins, 46 | esbuild.default({ 47 | target: 'node14', 48 | define: { 49 | __BROWSER__: 'true', 50 | }, 51 | }), 52 | ] 53 | 54 | const commonConfig = { 55 | input, 56 | external, 57 | treeshake: 'smallest', 58 | } 59 | 60 | const outputs = env => [{ 61 | dir: 'dist', 62 | format: 'esm', 63 | entryFileNames: `[name]${env ? `.${env}` : ''}.mjs`, 64 | }, { 65 | dir: 'dist', 66 | format: 'cjs', 67 | entryFileNames: `[name]${env ? `.${env}` : ''}.cjs`, 68 | }] 69 | 70 | export default () => defineConfig([ 71 | { 72 | ...commonConfig, 73 | output: outputs(), 74 | plugins: [ 75 | ...nodePlugins, 76 | ], 77 | }, 78 | { 79 | ...commonConfig, 80 | output: outputs('browser'), 81 | plugins: [ 82 | ...browserPlugins, 83 | ], 84 | }, 85 | { 86 | input, 87 | output: { 88 | dir: 'dist', 89 | entryFileNames: '[name].d.ts', 90 | format: 'esm', 91 | }, 92 | external, 93 | plugins: [ 94 | dts({ respectExternal: true }), 95 | ], 96 | }, 97 | { 98 | input, 99 | output: { 100 | dir: 'dist', 101 | entryFileNames: '[name].browser.d.ts', 102 | format: 'esm', 103 | }, 104 | external, 105 | treeshake: 'smallest', 106 | plugins: [ 107 | esbuild.default({ 108 | target: 'node14', 109 | define: { 110 | __BROWSER__: 'true', 111 | }, 112 | }), 113 | dts({ respectExternal: true }), 114 | ], 115 | }, 116 | ]) 117 | -------------------------------------------------------------------------------- /src/api/deposit_bounty.ts: -------------------------------------------------------------------------------- 1 | import { Contract, ethers } from 'ethers' 2 | import { 3 | cleContractABI, 4 | } from '../common/constants' 5 | 6 | /** 7 | * @param {number} depositAmount - the deposit amount in ETH 8 | */ 9 | interface DepositOptions { 10 | depositAmount: string 11 | } 12 | /** 13 | * Publish and register CLE onchain. 14 | * @param {Signer} signer - the acct for sign tx 15 | * @param {string} cleContractAddress - the deployed verification contract address 16 | * @param options 17 | * @returns {string} - transaction hash of the publish transaction if success, empty string otherwise 18 | */ 19 | export async function deposit( 20 | cleContractAddress: string, 21 | signer: ethers.Signer, 22 | options: DepositOptions, 23 | ) { 24 | const { depositAmount } = options 25 | 26 | const cleContract = new Contract(cleContractAddress, cleContractABI, signer) 27 | const tx = await cleContract 28 | .deposit( 29 | ethers.utils.parseEther(depositAmount), { value: ethers.utils.parseEther(depositAmount) }, 30 | ) 31 | .catch((err: any) => { 32 | throw err 33 | }) 34 | 35 | const txReceipt = await tx.wait(1).catch((err: any) => { 36 | throw err 37 | }) 38 | 39 | return txReceipt as { 40 | transactionHash: string 41 | blockNumber: number 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/api/exec.ts: -------------------------------------------------------------------------------- 1 | import { Input, Simulator, instantiateWasm, setupZKWasmSimulator } from 'zkwasm-toolchain' 2 | // import { instantiateWasm, setupZKWasmMock } from '../common/bundle' 3 | 4 | import { DSPNotFound } from '../common/error' 5 | import { dspHub } from '../dsp/hub' 6 | import type { DataPrep } from '../dsp/interface' 7 | import type { CLEExecutable } from '../types' 8 | export { hasDebugOnlyFunc } from 'zkwasm-toolchain' 9 | 10 | /** 11 | * Execute the given cleExecutable in the context of execParams 12 | * @param {object} cleExecutable {'cleYaml': cleYaml} 13 | * @param {object} execParams 14 | * @returns {Uint8Array} - execution result (aka. CLE state) 15 | */ 16 | export async function execute(cleExecutable: CLEExecutable, execParams: Record): Promise { 17 | const { cleYaml } = cleExecutable 18 | 19 | const dsp /** :DataSourcePlugin */ = dspHub.getDSPByYaml(cleYaml) 20 | if (!dsp) 21 | throw new DSPNotFound('Can\'t find DSP for this data source kind.') 22 | 23 | const prepareParams = await dsp?.toPrepareParams(execParams, 'exec') 24 | const dataPrep /** :DataPrep */ = await dsp?.prepareData(cleYaml, prepareParams) as DataPrep 25 | 26 | return await executeOnDataPrep(cleExecutable, dataPrep) 27 | } 28 | 29 | /** 30 | * 31 | * @param {object} cleExecutable 32 | * @param {DataPrep} dataPrep 33 | * @returns 34 | */ 35 | export async function executeOnDataPrep(cleExecutable: CLEExecutable, dataPrep: DataPrep): Promise { 36 | const { cleYaml } = cleExecutable 37 | 38 | let input = new Input() 39 | 40 | const dsp /** :DataSourcePlugin */ = dspHub.getDSPByYaml(cleYaml) 41 | if (!dsp) 42 | throw new DSPNotFound('Can\'t find DSP for this data source kind.') 43 | 44 | input = dsp.fillExecInput(input, cleYaml, dataPrep) 45 | 46 | return await executeOnInputs(cleExecutable, input) 47 | } 48 | 49 | /** 50 | * 51 | * @param {object} cleExecutable 52 | * @param {string} privateInputStr 53 | * @param {string} publicInputStr 54 | * @returns 55 | */ 56 | export async function executeOnInputs(cleExecutable: CLEExecutable, input: Input): Promise { 57 | // console.log('executeOnInputs input:', input.auxParams) 58 | 59 | const { wasmUint8Array } = cleExecutable 60 | if (!wasmUint8Array) 61 | throw new Error('wasmUint8Array is null') 62 | const mock = new Simulator(100000000, 2000) 63 | mock.set_private_input(input.getPrivateInputStr()) 64 | mock.set_public_input(input.getPublicInputStr()) 65 | mock.set_context_input(input.getContextInputStr()) 66 | setupZKWasmSimulator(mock) 67 | 68 | const { asmain } = await instantiateWasm(wasmUint8Array).catch((error: any) => { 69 | throw error 70 | }) 71 | 72 | let stateU8a 73 | // eslint-disable-next-line no-useless-catch 74 | try { 75 | stateU8a = asmain() 76 | } 77 | catch (e) { 78 | throw e 79 | } 80 | return stateU8a as Uint8Array 81 | } 82 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export { execute, executeOnDataPrep, executeOnInputs, hasDebugOnlyFunc } from './exec' 2 | export { proveInputGen, proveInputGenOnDataPrep } from './prove_inputgen' 3 | export { proveMock } from './prove_mock' 4 | export { prove, requestProve, waitProve } from './prove' 5 | export { upload } from './upload' 6 | export { setup, requestSetup, waitSetup, requireImageDetails } from './setup' 7 | export { publish, publishByImgCmt, getImageCommitment } from './publish' 8 | export { deposit } from './deposit_bounty' 9 | export { verify, verifyOnchain as verifyProof, getVerifyProofParamsByTaskID } from './verify' 10 | export { TaskDispatch } from './task_dispatcher' 11 | export * from './compile' 12 | export * from './trigger' 13 | -------------------------------------------------------------------------------- /src/api/prove.ts: -------------------------------------------------------------------------------- 1 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 2 | import type { Input } from 'zkwasm-toolchain' 3 | import { ora_prove } from '../requests/ora_prove' 4 | import { 5 | waitTaskStatus, 6 | } from '../requests/zkwasm_taskdetails' 7 | import type { BatchOption, CLEExecutable, ProofParams, SingableProver } from '../types' 8 | import { BatchStyle } from '../types' 9 | import { logger } from '../common' 10 | import { PROVER_RPC_CONSTANTS } from '../common/constants' 11 | import { requireImageDetails } from './setup' 12 | 13 | export type ProveOptions = SingableProver & BatchOption 14 | 15 | export interface ProveResult { 16 | status: string 17 | proofParams?: ProofParams 18 | taskDetails?: any // optional 19 | } 20 | 21 | /** 22 | * Submit prove task to a given zkwasm and return the proof details. 23 | */ 24 | export async function prove( 25 | cleExecutable: Omit, 26 | input: Input, 27 | options: ProveOptions, 28 | ): Promise { 29 | const prResult = await requestProve(cleExecutable, input, options) 30 | const pwResult = await waitProve(options.proverUrl, prResult.taskId, options) 31 | return pwResult 32 | } 33 | 34 | export interface RequestProveResult { 35 | md5: string 36 | taskId: string 37 | taskDetails?: any // optional 38 | } 39 | 40 | export async function requestProve( 41 | cleExecutable: Omit, 42 | input: Input, 43 | options: ProveOptions, 44 | ): Promise { 45 | const { wasmUint8Array } = cleExecutable 46 | 47 | const md5 = ZkWasmUtil.convertToMd5(wasmUint8Array).toUpperCase() 48 | logger.log(`[*] IMAGE MD5: ${md5}`, '\n') 49 | 50 | // require image exist & valid 51 | await requireImageDetails(options.proverUrl, md5) 52 | 53 | let taskId = '' // taskId must be set to response.data.result.id later 54 | const response = await ora_prove(md5, input, options) 55 | taskId = response.data.result.id 56 | logger.log(`[+] PROVING TASK STARTED. TASK ID: ${taskId}`, '\n') 57 | // TODO: other error types need to be catch here? e.g. NoSetup 58 | 59 | const result: RequestProveResult = { md5, taskId } 60 | return result 61 | // const privateInputArray = input.getPrivateInputStr().trim().split(' ') 62 | // const publicInputArray = input.getPublicInputStr().trim().split(' ') 63 | // const [response, isSetUpSuccess, errorMessage] = await zkwasm_prove( 64 | // zkwasmProverUrl, 65 | // userPrivateKey, 66 | // md5, 67 | // publicInputArray, 68 | // privateInputArray, 69 | // ).catch((error) => { 70 | // throw error 71 | // }) 72 | } 73 | 74 | export async function waitProve( 75 | proverUrl: string, 76 | proveTaskId: string, 77 | options: BatchOption = {}, 78 | ): Promise { 79 | const { batchStyle = BatchStyle.ZKWASMHUB } = options 80 | const task = await waitTaskStatus( 81 | proverUrl, 82 | proveTaskId, 83 | PROVER_RPC_CONSTANTS.TASK_STATUS_PROVE_FINISH_LIST, 84 | 3000, 0) 85 | // .catch((err) => { 86 | // throw err 87 | // }) // TODO: timeout 88 | 89 | const result: ProveResult = { 90 | status: task.status, 91 | proofParams: undefined, 92 | taskDetails: task, 93 | } 94 | 95 | if (task.status === PROVER_RPC_CONSTANTS.TASK_STATUS_DONE) { 96 | const proofParams: ProofParams = { 97 | aggregate_proof: task.proof, 98 | batch_instances: task.batch_instances, 99 | aux: task.aux, 100 | // 2-dim, ZKWASMHUB-compatible 101 | instances: batchStyle === BatchStyle.ZKWASMHUB ? [task.instances] : task.instances, 102 | extra: task?.extra, 103 | } 104 | result.proofParams = proofParams 105 | } 106 | 107 | return result 108 | } 109 | -------------------------------------------------------------------------------- /src/api/prove_inputgen.ts: -------------------------------------------------------------------------------- 1 | import { Input } from 'zkwasm-toolchain' 2 | import { DSPNotFound } from '../common/error' 3 | import { dspHub } from '../dsp/hub' 4 | import type { DataPrep } from '../dsp/interface' 5 | import type { CLEExecutable } from '../types' 6 | 7 | /** 8 | * Generate the private and public inputs in hex string format 9 | * @param {object} cleExecutable 10 | * @param {object} proveParams {"xx": xx} 11 | * @returns {[string, string]} - private input string, public input string 12 | */ 13 | export async function proveInputGen( 14 | cleExecutable: Omit, 15 | proveParams: Record, 16 | ): Promise { 17 | const { cleYaml } = cleExecutable 18 | 19 | const dsp /** :DataSourcePlugin */ = dspHub.getDSPByYaml(cleYaml) 20 | if (!dsp) 21 | throw new DSPNotFound('Can\'t find DSP for this data source kind.') 22 | 23 | const prepareParams = await dsp?.toPrepareParams(proveParams, 'prove') 24 | const dataPrep /** :DataPrep */ = await dsp?.prepareData(cleYaml, prepareParams) as DataPrep 25 | 26 | return proveInputGenOnDataPrep(cleExecutable, dataPrep) 27 | } 28 | 29 | export function proveInputGenOnDataPrep( 30 | cleExecutable: Omit, 31 | dataPrep: DataPrep, 32 | ): Input { 33 | const { cleYaml } = cleExecutable 34 | 35 | let input = new Input() 36 | 37 | const dsp /** :DataSourcePlugin */ = dspHub.getDSPByYaml(cleYaml) 38 | if (!dsp) 39 | throw new DSPNotFound('Can\'t find DSP for this data source kind.') 40 | 41 | input = dsp.fillProveInput(input, cleYaml, dataPrep) 42 | 43 | // return [input.getPrivateInputStr(), input.getPublicInputStr()] 44 | return input 45 | } 46 | -------------------------------------------------------------------------------- /src/api/prove_mock.ts: -------------------------------------------------------------------------------- 1 | import type { Input } from 'zkwasm-toolchain' 2 | import { Simulator, instantiateWasm, setupZKWasmSimulator } from 'zkwasm-toolchain' 3 | import { CLERequireFailed } from '../common/error' 4 | import type { CLEExecutable } from '../types' 5 | 6 | /** 7 | * Mock the zkwasm proving process for pre-test purpose. 8 | * @param {object} cleExecutable 9 | * @param {Input} input 10 | * @returns {boolean} - the mock testing result 11 | */ 12 | export async function proveMock( 13 | cleExecutable: Omit, 14 | input: Input, 15 | ): Promise { 16 | const { wasmUint8Array } = cleExecutable 17 | 18 | const mock = new Simulator() 19 | mock.set_private_input(input.getPrivateInputStr()) 20 | mock.set_public_input(input.getPublicInputStr()) 21 | setupZKWasmSimulator(mock) 22 | 23 | const { zkmain } = await instantiateWasm(wasmUint8Array).catch((error: any) => { 24 | throw error 25 | }) 26 | 27 | try { 28 | zkmain() 29 | } 30 | catch (e) { 31 | if (e instanceof CLERequireFailed) 32 | return false 33 | 34 | throw e 35 | } 36 | 37 | return true 38 | } 39 | -------------------------------------------------------------------------------- /src/api/publish.ts: -------------------------------------------------------------------------------- 1 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 2 | import { Contract, ethers, utils } from 'ethers' 3 | import { 4 | AddressZero, 5 | DEFAULT_URL, 6 | EventSigNewCLE, 7 | abiFactory, 8 | addressFactory, 9 | } from '../common/constants' 10 | import { CLEAlreadyExist, DSPNotFound, TxFailed } from '../common/error' 11 | import { dspHub } from '../dsp/hub' 12 | import type { CLEExecutable, CLEYaml } from '../types' 13 | import { requireImageDetails } from './setup' 14 | 15 | export interface PublishOptions { 16 | proverUrl?: string 17 | ipfsHash: string // the ipfs hash from the 'upload' step 18 | bountyRewardPerTrigger: number // the bounty reward per verified trigger (ETH) 19 | } 20 | 21 | export interface PublishResult { 22 | graphAddress?: string // deprecating. == cleAddress 23 | cleAddress: string 24 | blockNumber: number 25 | transactionHash: string 26 | networkName: string 27 | } 28 | 29 | /** 30 | * Publish and register CLE onchain. 31 | * @param {object} cleExecutable {wasmUint8Array, cleYaml} 32 | * @param {ethers.Signer} signer - the acct for sign tx 33 | * @param options 34 | * @returns 35 | */ 36 | export async function publish( 37 | cleExecutable: CLEExecutable, 38 | signer: ethers.Signer, 39 | options: PublishOptions, 40 | ): Promise { 41 | const { proverUrl = DEFAULT_URL.PROVER } = options 42 | const imgCmt = await getImageCommitment(cleExecutable, proverUrl) 43 | return publishByImgCmt(cleExecutable, signer, options, imgCmt) 44 | } 45 | 46 | /** 47 | * Publish and register CLE onchain, with code hash provided. 48 | * @param {object} cleExecutable {cleYaml} 49 | * @param {object} signer - the acct for sign tx 50 | * @param options 51 | * @param imageCommitment 52 | */ 53 | export async function publishByImgCmt( 54 | cleExecutable: CLEExecutable, 55 | signer: ethers.Signer, 56 | options: PublishOptions, 57 | imageCommitment: { pointX: ethers.BigNumber; pointY: ethers.BigNumber }, 58 | ): Promise { 59 | const { ipfsHash, bountyRewardPerTrigger = 0.05 } = options 60 | const { cleYaml } = cleExecutable 61 | 62 | const dsp = dspHub.getDSPByYaml(cleYaml) 63 | if (!dsp) 64 | throw new DSPNotFound('Can\'t find DSP for this data source kind.') 65 | 66 | // for ora prover upgrade 67 | const suffix = (cy: CLEYaml) => { 68 | const allEthDS = cy.dataSources.filter( 69 | ds => ds.kind === 'ethereum')// ds.filterByKeys(['event', 'storage', 'transaction']) // 70 | const allEthDSState = allEthDS.filter( 71 | ds => Object.keys(ds.filterByKeys(['storage'])).length !== 0) 72 | const allEthDSStateOnly = allEthDSState.filter( 73 | ds => Object.keys(ds.filterByKeys(['event', 'transaction'])).length === 0) 74 | if (allEthDSStateOnly.length > 0) 75 | return ':stateonly' 76 | 77 | const allNoTx = allEthDS.filter( 78 | ds => Object.keys(ds.filterByKeys(['transaction'])).length === 0) 79 | if (allNoTx.length > 0) 80 | return ':notx' 81 | 82 | return '' 83 | } 84 | // logger.debug('[*] dsp name suffix for clecontract:', suffix(cleYaml)) 85 | 86 | const dspID = utils.keccak256(utils.toUtf8Bytes(dsp.getLibDSPName() + suffix(cleYaml))) 87 | 88 | const destinationContractAddress 89 | = (cleYaml?.dataDestinations && cleYaml?.dataDestinations.length) 90 | ? cleYaml?.dataDestinations[0].address 91 | : AddressZero 92 | const networkName = (await signer.provider?.getNetwork())?.name 93 | const factoryAddress = networkName ? (addressFactory as any)[networkName] : AddressZero 94 | const factoryContract = new Contract(factoryAddress, abiFactory, signer) 95 | const bountyReward = ethers.utils.parseEther(bountyRewardPerTrigger.toString()) 96 | 97 | const tx = await factoryContract 98 | .registry( 99 | destinationContractAddress, 100 | AddressZero, 101 | bountyReward, 102 | dspID, 103 | ipfsHash, 104 | imageCommitment.pointX, 105 | imageCommitment.pointY, 106 | ) 107 | .catch((_err: any) => { 108 | throw new CLEAlreadyExist('Duplicate CLE detected. Only publishing distinct CLEs is allowed.') 109 | }) 110 | 111 | const txReceipt = await tx.wait(1).catch((err: any) => { 112 | throw err 113 | }) 114 | 115 | if (txReceipt.status !== 1) 116 | throw new TxFailed(`Transaction failed (${txReceipt.transactionHash})`) 117 | 118 | // filter event with topic "NewCLE(address)" in transaction receipt 119 | const logs = txReceipt.logs.filter((log: { address: string; topics: string[] }) => 120 | log.address === factoryAddress 121 | && log.topics[0] === EventSigNewCLE, 122 | ) 123 | 124 | if (logs.length === 0) 125 | throw new Error(`Can't identify NewCLE(address) event in tx receipt (${txReceipt.transactionHash}), please check the TX or factory contract (${factoryAddress})`) 126 | 127 | // Extract the cle address from the event 128 | const cleAddress = `0x${logs[0].data.slice(-40)}` 129 | 130 | return { 131 | cleAddress, 132 | graphAddress: cleAddress, // for backward compatible only, rm when deprecate 133 | ...txReceipt, 134 | } as PublishResult 135 | } 136 | 137 | function littleEndianToUint256(inputArray: number[]): ethers.BigNumber { 138 | const reversedArray = inputArray.reverse() 139 | const hexString = `0x${reversedArray.map(byte => byte.toString(16).padStart(2, '0')).join('')}` 140 | 141 | return ethers.BigNumber.from(hexString) 142 | } 143 | 144 | /** 145 | * 146 | * @param {object} cleExecutable {wasmUint8Array} 147 | * @param {string} proverUrl - the prover rpc url 148 | * @returns 149 | */ 150 | export async function getImageCommitment( 151 | cleExecutable: CLEExecutable, 152 | proverUrl: string, 153 | ) { 154 | const { wasmUint8Array } = cleExecutable 155 | const md5 = ZkWasmUtil.convertToMd5(wasmUint8Array).toLowerCase() 156 | const details = await requireImageDetails(proverUrl, md5) 157 | const pointX = littleEndianToUint256(details.checksum.x) 158 | const pointY = littleEndianToUint256(details.checksum.y) 159 | return { pointX, pointY } 160 | } 161 | -------------------------------------------------------------------------------- /src/api/setup.ts: -------------------------------------------------------------------------------- 1 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 2 | import { 3 | waitTaskStatus, 4 | } from '../requests/zkwasm_taskdetails' 5 | import { CircuitSizeOutOfRange, ImageAlreadyExists, ImageInvalid, ImageNotExists } from '../common/error' 6 | import { zkwasm_imagetask } from '../requests/zkwasm_imagetask' 7 | import type { CLEExecutable, SingableProver } from '../types' 8 | import { ora_setup, zkwasm_imagedetails } from '../requests' 9 | import { createFileStream } from '../common/compatible' 10 | import { DEFAULT_CIRCUIT_SIZE, DEFAULT_URL, MAX_CIRCUIT_SIZE, MIN_CIRCUIT_SIZE, PROVER_RPC_CONSTANTS } from '../common/constants' 11 | import { logger } from '../common' 12 | 13 | export interface BasicSetupParams { 14 | circuitSize?: number 15 | imageName?: string // optional, can diff from local files 16 | descriptionUrl?: string 17 | avatorUrl?: string 18 | } 19 | 20 | export type SetupOptions = SingableProver & BasicSetupParams 21 | 22 | export interface SetupResult { 23 | status: string 24 | success: boolean 25 | taskDetails?: any // optional 26 | } 27 | 28 | export async function setup( 29 | cleExecutable: Omit, 30 | options: SetupOptions, 31 | ): Promise { 32 | const rsResult = await requestSetup(cleExecutable, options) 33 | const wsResult = await waitSetup(options.proverUrl, rsResult.taskId) 34 | return wsResult 35 | } 36 | 37 | export interface RequestSetupResult { 38 | md5: string 39 | taskId: string 40 | taskDetails?: any // optional 41 | } 42 | 43 | /** 44 | * Set up zkwasm image with given wasm file. 45 | */ 46 | export async function requestSetup( 47 | cleExecutable: Omit, 48 | options: SetupOptions, 49 | ): Promise { 50 | const { wasmUint8Array } = cleExecutable 51 | const { 52 | circuitSize = DEFAULT_CIRCUIT_SIZE, 53 | proverUrl = DEFAULT_URL.PROVER, 54 | } = options 55 | const image = createFileStream(wasmUint8Array, { fileType: 'application/wasm', fileName: 'cle.wasm' }) 56 | 57 | if (circuitSize < MIN_CIRCUIT_SIZE || circuitSize > MAX_CIRCUIT_SIZE) 58 | throw new CircuitSizeOutOfRange('Circuit size out of range. Please set it between 18 and 24.') 59 | 60 | const md5 = ZkWasmUtil.convertToMd5(wasmUint8Array).toLowerCase() 61 | 62 | let taskDetails 63 | let taskId = '' 64 | // let setupStatus 65 | 66 | await ora_setup( 67 | md5, 68 | image, 69 | options, 70 | ) 71 | .then(async (response) => { 72 | taskId = response.data.result.id 73 | taskDetails = response.data.result.data[0] 74 | // result.status = response.data.result.data[0].status 75 | 76 | logger.log(`[+] SET UP TASK STARTED. TASK ID: ${taskId}`, '\n') 77 | }) 78 | .catch(async (error) => { 79 | // return the last status if exists 80 | if (error instanceof ImageAlreadyExists) { 81 | // check if there's any "Reset" task before 82 | let res = await zkwasm_imagetask(proverUrl, md5, PROVER_RPC_CONSTANTS.TASK_TYPE_RESET) 83 | // if no "Reset", check "Setup" 84 | if (res.data.result.total === 0) 85 | res = await zkwasm_imagetask(proverUrl, md5, PROVER_RPC_CONSTANTS.TASK_TYPE_SETUP) 86 | 87 | taskDetails = res.data.result.data[0] 88 | taskId = res.data.result.data[0]._id.$oid 89 | // setupStatus = res.data.result.data[0].status 90 | 91 | logger.log(`[*] IMAGE ALREADY EXISTS. PREVIOUS SETUP TASK ID: ${taskId}`, '\n') 92 | } 93 | else { 94 | throw error 95 | } 96 | }) 97 | 98 | const result: RequestSetupResult = { 99 | md5, 100 | taskId, 101 | taskDetails, 102 | } 103 | return result 104 | } 105 | 106 | export async function waitSetup(proverUrl: string, taskId: string): Promise { 107 | const taskDetails = await waitTaskStatus( 108 | proverUrl, 109 | taskId, 110 | PROVER_RPC_CONSTANTS.TASK_STATUS_SETUP_FINISH_LIST, 111 | 3000, 112 | 0, 113 | ) // TODO: timeout 114 | 115 | const result: SetupResult = { 116 | success: taskDetails.status === PROVER_RPC_CONSTANTS.TASK_STATUS_DONE, 117 | status: taskDetails.status, 118 | taskDetails, 119 | } 120 | return result 121 | } 122 | 123 | export async function requireImageDetails(proverUrl: string, md5: string) { 124 | const response = await zkwasm_imagedetails(proverUrl, md5) 125 | const details = response[0]?.data.result[0] 126 | if (details === null) 127 | throw new ImageNotExists('Can\'t find wasm image in prover, please finish setup first.') 128 | if (!PROVER_RPC_CONSTANTS.IMAGE_STATUS_VALID_LIST.includes(details.status)) 129 | throw new ImageInvalid('wasm image is invalid in prover, please setup a valid wasm. or contact admin if you believe it should be valid.') 130 | 131 | return details 132 | } 133 | -------------------------------------------------------------------------------- /src/api/task_dispatcher.ts: -------------------------------------------------------------------------------- 1 | import type { providers } from 'ethers' 2 | import { ethers } from 'ethers' 3 | import type { AxiosInstance } from 'axios' 4 | import axios from 'axios' 5 | import axiosRetry from 'axios-retry' 6 | import { polling } from '@murongg/utils' 7 | import { InsufficientBalance, TDNoTaskFound } from '../common/error' 8 | 9 | const ABI = [ 10 | 'function setup(string memory imageId, uint256 circuitSize) payable', 11 | 'function prove(string memory imageId, string memory privateInput, string memory publicInput) payable', 12 | ] 13 | 14 | export interface QueryTaskResponse { 15 | task?: { 16 | id?: string 17 | type: 'setup' | 'prove' 18 | status: 'processing' | 'submitted' 19 | } 20 | } 21 | 22 | export class TaskDispatch { 23 | queryAPI: string 24 | feeInWei: ethers.BigNumber 25 | dispatcherContract: ethers.Contract 26 | private request: AxiosInstance 27 | 28 | constructor(queryAPI: string, contractAddress: string, feeInWei: ethers.BigNumber, provider: providers.JsonRpcProvider, signer: ethers.Wallet | ethers.providers.Provider | string) { 29 | this.queryAPI = queryAPI 30 | this.feeInWei = feeInWei 31 | this.dispatcherContract = new ethers.Contract(contractAddress, ABI, provider).connect(signer) 32 | this.request = axios.create({ 33 | baseURL: queryAPI, 34 | }) 35 | axiosRetry(this.request, { 36 | retries: 5, 37 | retryDelay: (retryCount) => { 38 | return retryCount * 2000 39 | }, 40 | }) 41 | } 42 | 43 | /** 44 | * Query task id by txhash. 45 | */ 46 | async queryTask(txhash: string) { 47 | let res: QueryTaskResponse | undefined 48 | const request = async () => { 49 | const response = await this.request.get('/task', { 50 | params: { 51 | txhash, 52 | }, 53 | }) 54 | const data = response.data 55 | if (data.task?.status === 'submitted') { 56 | res = data 57 | return true 58 | } 59 | return false 60 | } 61 | await polling(request, 1000) 62 | if (!res?.task?.id && res?.task?.status === 'submitted') 63 | throw new TDNoTaskFound('No corresponding task found for the transaction') 64 | 65 | return res as QueryTaskResponse 66 | } 67 | 68 | /** 69 | * Setup wasm iamge. 70 | * @param {string} imageId 71 | * @param {number} circuitSize 72 | * @returns {Promise} 73 | */ 74 | async setup(imageId: string, circuitSize: number) { 75 | const balance = await this.dispatcherContract.signer.getBalance() 76 | if (balance.lt(this.feeInWei)) 77 | throw new InsufficientBalance('Insufficient balance in the connect wallet.') 78 | 79 | const tx = await this.dispatcherContract.setup(imageId, circuitSize, { 80 | value: this.feeInWei, 81 | }) 82 | return tx 83 | } 84 | 85 | /** 86 | * Prove task. 87 | * @param {string} imageId 88 | * @param {string} privateInput 89 | * @param {string} publicInput 90 | * @returns {Promise} 91 | */ 92 | async prove(imageId: string, privateInput: string, publicInput: string) { 93 | const balance = await this.dispatcherContract.signer.getBalance() 94 | if (balance.lt(this.feeInWei)) 95 | throw new InsufficientBalance('Insufficient balance in the connect wallet.') 96 | 97 | const tx = await this.dispatcherContract.prove(imageId, privateInput, publicInput, { 98 | value: this.feeInWei, 99 | }) 100 | return tx 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/api/trigger.ts: -------------------------------------------------------------------------------- 1 | import { ddpHub } from '../ddp/hub' 2 | import type { CLEExecutable, ProofParams } from '../types' 3 | 4 | /** 5 | * @dev experimental, only used for Ora Node, Non-public use, only works in Ora Batch Style 6 | */ 7 | export async function trigger( 8 | cleExecutable: Omit, 9 | cleId: string, 10 | proofParams: ProofParams, 11 | ddpParamsList: Record[], // 1 ddpParams per ddp 12 | ): Promise { 13 | const { cleYaml } = cleExecutable 14 | const ddps = ddpHub.getDDPsByYaml(cleYaml) 15 | if (ddpParamsList.length !== ddps.length) 16 | throw new Error(`The length of DDP params list provided (${ddpParamsList.length}) doesn't match yaml specified DDP numbers ${ddps.length} `) 17 | 18 | for (let i = 0; i < ddps.length; i++) 19 | await ddps[i].go(cleId, proofParams, ddpParamsList[i]) 20 | } 21 | -------------------------------------------------------------------------------- /src/api/upload.ts: -------------------------------------------------------------------------------- 1 | import type fs from 'fs' 2 | import FormData from 'form-data' 3 | import { isKeyOf, objectMap } from '@murongg/utils' 4 | import type { PinataOptions } from '../requests/pinata_upload' 5 | import { pinata_upload } from '../requests/pinata_upload' 6 | import { UploadFileNotExist } from '../common/error' 7 | import { DEFAULT_PATH } from '../common/constants' 8 | 9 | // export const UploadMustBeExistFiles = ['cle.yaml', 'cle.wasm'] 10 | 11 | export interface UploadRequiredFiles { 12 | yamlPath?: string 13 | outWasmPath?: string 14 | } 15 | /** 16 | * 17 | * @param {string} pinataEndpoint - The endpoint for the Pinata service. 18 | * @param {string} pinataJWT - The JWT token for authentication. 19 | * @param {string} directoryName - The name to identify the directory. 20 | */ 21 | 22 | export type UploadOptions = PinataOptions & UploadRequiredFiles 23 | 24 | export interface UploadResult { 25 | success: boolean 26 | isUploadSuccess?: boolean // deprecating. == success 27 | errorMessage?: string 28 | response: any 29 | } 30 | 31 | /** 32 | * Uploads files to a specified directory. 33 | * 34 | * @param {Record} files - The files to be uploaded. 35 | * @param {UploadOptions} opitons 36 | * @returns {Promise} - An object containing the response, upload success status, and error message (if any). 37 | */ 38 | export async function upload( 39 | files: Record, 40 | options: UploadOptions, 41 | ): Promise { 42 | const { pinataEndpoint, pinataJWT, directoryName } = options 43 | 44 | const { 45 | yamlPath = DEFAULT_PATH.YAML, 46 | outWasmPath = DEFAULT_PATH.OUT_WASM, 47 | } = options 48 | // check filename validity 49 | const UploadMustBeExistFiles = [yamlPath, outWasmPath] 50 | UploadMustBeExistFiles.forEach((filename) => { 51 | if (!isKeyOf(files, filename)) 52 | throw new UploadFileNotExist(`File ${filename} does not exist.`) 53 | }) 54 | 55 | // setup form data 56 | // const directoryName = `${directoryTag.graphName}-${directoryTag.userAddress}` 57 | const formData = new FormData() 58 | objectMap(files, (key: string, value: File | fs.ReadStream) => { 59 | formData.append('file', value, __BROWSER__ 60 | ? `${directoryName}/${key}` 61 | : { 62 | filepath: `${directoryName}/${key}`, 63 | }) 64 | return undefined 65 | }) 66 | const metadata = JSON.stringify({ 67 | name: directoryName, 68 | }) 69 | formData.append('pinataMetadata', metadata) 70 | // upload 71 | const pinataresult = await pinata_upload( 72 | formData, 73 | pinataEndpoint, 74 | pinataJWT, 75 | ).catch((error) => { 76 | throw error 77 | }) 78 | 79 | return pinataresult 80 | } 81 | -------------------------------------------------------------------------------- /src/api/verify.ts: -------------------------------------------------------------------------------- 1 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 2 | import type { providers } from 'ethers' 3 | import { Contract } from 'ethers' 4 | import { AggregatorVerifierABI, AggregatorVerifierAddress, PROVER_RPC_CONSTANTS } from '../common/constants' 5 | import { ProveTaskNotReady } from '../common/error' 6 | import type { BatchOption, ProofParams, ProofParams as VerifyProofParams } from '../types' 7 | import { BatchStyle } from '../types' 8 | import { getNetworkNameByChainID } from '../common/utils' 9 | import { waitProve } from './prove' 10 | // import { VerifyProofParams } from '@ora-io/zkwasm-service-helper' 11 | 12 | export interface OnchainVerifier { 13 | provider: providers.JsonRpcProvider 14 | verifierAddress?: string 15 | isZKVerifier?: boolean // vs. CLEVerifier 16 | } 17 | 18 | export type VerifyOptions = OnchainVerifier & BatchOption 19 | 20 | export async function verify( 21 | verifyParams: VerifyProofParams, 22 | options: VerifyOptions, 23 | ) { 24 | return await verifyOnchain(verifyParams, options) 25 | } 26 | 27 | /** 28 | * Verify zk proof with eth call. 29 | * @param verifyParams 30 | * @param options 31 | * @returns 32 | */ 33 | export async function verifyOnchain( 34 | verifyParams: VerifyProofParams, 35 | options: VerifyOptions, 36 | ): Promise { 37 | const { batchStyle = BatchStyle.ZKWASMHUB, isZKVerifier = true } = options 38 | if (isZKVerifier === false) 39 | throw new Error('isZKVerifier==false is reserved, not supported yet') 40 | const { provider } = options 41 | 42 | const chainId = (await provider.getNetwork()).chainId 43 | const network = getNetworkNameByChainID(chainId).toLowerCase() 44 | const defaultVerifierAddress 45 | = batchStyle === BatchStyle.ZKWASMHUB 46 | ? AggregatorVerifierAddress.ZKWASMHUB[network] 47 | : batchStyle === BatchStyle.ORA_SINGLE 48 | ? AggregatorVerifierAddress.ORA_SINGLE[network] 49 | : AggregatorVerifierAddress.ORA[network] 50 | 51 | const { verifierAddress = defaultVerifierAddress } = options 52 | 53 | const proof = ZkWasmUtil.bytesToBigIntArray(verifyParams.aggregate_proof) 54 | const instances = ZkWasmUtil.bytesToBigIntArray(verifyParams.batch_instances) 55 | const aux = ZkWasmUtil.bytesToBigIntArray(verifyParams.aux) 56 | // const arg = ZkWasmUtil.bytesToBigIntArray(verifyParams.instances) 57 | // TODO: cli compatible 58 | const arg = verifyParams.instances.map((ins) => { return ZkWasmUtil.bytesToBigIntArray(ins) }) 59 | // const arg = decode2DProofParam(verifyParams.instances) 60 | const extra = verifyParams.extra ? ZkWasmUtil.bytesToBigIntArray(verifyParams.extra) : undefined 61 | 62 | if (batchStyle === BatchStyle.ORA && extra === undefined) 63 | throw new Error('missing \'extra\' params under ORA batch style') 64 | 65 | const verifyCallParam 66 | // = batchStyle === BatchStyle.ORA 67 | = isZKVerifier 68 | ? [proof, instances, aux, arg] // ZKVerifier doesn't care extra 69 | : [proof, instances, aux, arg, extra] // CLEVerifier needs extra 70 | 71 | // Web3EthContract.setProvider(jsonRpcProviderUrl) 72 | // const contract = new Web3EthContract(AggregatorVerifierABI.abi as any, verifierContractAddress) 73 | const contract = new Contract(verifierAddress, AggregatorVerifierABI.abi as any, provider) 74 | 75 | // verify success if no err throw 76 | let verificationResult = true 77 | await contract 78 | .verify(...verifyCallParam) 79 | .catch((err: any) => { 80 | if (err.message.startsWith('call revert exception;')) 81 | // if (err.message.startsWith('Returned error: execution reverted')) 82 | verificationResult = false 83 | else 84 | throw err 85 | }) 86 | 87 | return verificationResult 88 | } 89 | 90 | export async function getVerifyProofParamsByTaskID( 91 | proverUrl: string, 92 | proveTaskId: string, 93 | options: BatchOption = {}, 94 | ): Promise { 95 | const result = await waitProve(proverUrl, proveTaskId, options) 96 | if (result.status !== PROVER_RPC_CONSTANTS.TASK_STATUS_DONE || !result.proofParams) 97 | throw new ProveTaskNotReady(`Prove task is not \'${PROVER_RPC_CONSTANTS.TASK_STATUS_DONE}\', can\'t verify`) 98 | return result.proofParams 99 | } 100 | -------------------------------------------------------------------------------- /src/common/abi/clecontractabi.ts: -------------------------------------------------------------------------------- 1 | export const cleContractABI = [ 2 | { 3 | type: 'constructor', 4 | inputs: [], 5 | stateMutability: 'nonpayable', 6 | }, 7 | { 8 | type: 'function', 9 | name: 'bountyReward', 10 | inputs: [], 11 | outputs: [ 12 | { 13 | name: '', 14 | type: 'uint256', 15 | internalType: 'uint256', 16 | }, 17 | ], 18 | stateMutability: 'view', 19 | }, 20 | { 21 | type: 'function', 22 | name: 'bountyToken', 23 | inputs: [], 24 | outputs: [ 25 | { 26 | name: '', 27 | type: 'address', 28 | internalType: 'address', 29 | }, 30 | ], 31 | stateMutability: 'view', 32 | }, 33 | { 34 | type: 'function', 35 | name: 'cleURI', 36 | inputs: [], 37 | outputs: [ 38 | { 39 | name: '', 40 | type: 'string', 41 | internalType: 'string', 42 | }, 43 | ], 44 | stateMutability: 'view', 45 | }, 46 | { 47 | type: 'function', 48 | name: 'deposit', 49 | inputs: [ 50 | { 51 | name: 'amount', 52 | type: 'uint256', 53 | internalType: 'uint256', 54 | }, 55 | ], 56 | outputs: [], 57 | stateMutability: 'payable', 58 | }, 59 | { 60 | type: 'function', 61 | name: 'destAddr', 62 | inputs: [], 63 | outputs: [ 64 | { 65 | name: '', 66 | type: 'address', 67 | internalType: 'address', 68 | }, 69 | ], 70 | stateMutability: 'view', 71 | }, 72 | { 73 | type: 'function', 74 | name: 'dspID', 75 | inputs: [], 76 | outputs: [ 77 | { 78 | name: '', 79 | type: 'bytes32', 80 | internalType: 'bytes32', 81 | }, 82 | ], 83 | stateMutability: 'view', 84 | }, 85 | { 86 | type: 'function', 87 | name: 'encoding_scalars_to_point', 88 | inputs: [ 89 | { 90 | name: 'a', 91 | type: 'uint256', 92 | internalType: 'uint256', 93 | }, 94 | { 95 | name: 'b', 96 | type: 'uint256', 97 | internalType: 'uint256', 98 | }, 99 | { 100 | name: 'c', 101 | type: 'uint256', 102 | internalType: 'uint256', 103 | }, 104 | ], 105 | outputs: [ 106 | { 107 | name: 'x', 108 | type: 'uint256', 109 | internalType: 'uint256', 110 | }, 111 | { 112 | name: 'y', 113 | type: 'uint256', 114 | internalType: 'uint256', 115 | }, 116 | ], 117 | stateMutability: 'pure', 118 | }, 119 | { 120 | type: 'function', 121 | name: 'factory', 122 | inputs: [], 123 | outputs: [ 124 | { 125 | name: '', 126 | type: 'address', 127 | internalType: 'address', 128 | }, 129 | ], 130 | stateMutability: 'view', 131 | }, 132 | { 133 | type: 'function', 134 | name: 'owner', 135 | inputs: [], 136 | outputs: [ 137 | { 138 | name: '', 139 | type: 'address', 140 | internalType: 'address', 141 | }, 142 | ], 143 | stateMutability: 'view', 144 | }, 145 | { 146 | type: 'function', 147 | name: 'pointX', 148 | inputs: [], 149 | outputs: [ 150 | { 151 | name: '', 152 | type: 'uint256', 153 | internalType: 'uint256', 154 | }, 155 | ], 156 | stateMutability: 'view', 157 | }, 158 | { 159 | type: 'function', 160 | name: 'pointY', 161 | inputs: [], 162 | outputs: [ 163 | { 164 | name: '', 165 | type: 'uint256', 166 | internalType: 'uint256', 167 | }, 168 | ], 169 | stateMutability: 'view', 170 | }, 171 | { 172 | type: 'function', 173 | name: 'renounceOwnership', 174 | inputs: [], 175 | outputs: [], 176 | stateMutability: 'nonpayable', 177 | }, 178 | { 179 | type: 'function', 180 | name: 'transferOwnership', 181 | inputs: [ 182 | { 183 | name: 'newOwner', 184 | type: 'address', 185 | internalType: 'address', 186 | }, 187 | ], 188 | outputs: [], 189 | stateMutability: 'nonpayable', 190 | }, 191 | { 192 | type: 'function', 193 | name: 'trigger', 194 | inputs: [ 195 | { 196 | name: 'proof', 197 | type: 'uint256[]', 198 | internalType: 'uint256[]', 199 | }, 200 | { 201 | name: 'verifyInstance', 202 | type: 'uint256[]', 203 | internalType: 'uint256[]', 204 | }, 205 | { 206 | name: 'aux', 207 | type: 'uint256[]', 208 | internalType: 'uint256[]', 209 | }, 210 | { 211 | name: 'targetInstance', 212 | type: 'uint256[][]', 213 | internalType: 'uint256[][]', 214 | }, 215 | { 216 | name: 'extra', 217 | type: 'uint256[]', 218 | internalType: 'uint256[]', 219 | }, 220 | ], 221 | outputs: [], 222 | stateMutability: 'nonpayable', 223 | }, 224 | { 225 | type: 'function', 226 | name: 'updateReward', 227 | inputs: [ 228 | { 229 | name: 'amount', 230 | type: 'uint256', 231 | internalType: 'uint256', 232 | }, 233 | ], 234 | outputs: [], 235 | stateMutability: 'nonpayable', 236 | }, 237 | { 238 | type: 'function', 239 | name: 'verifyToCalldata', 240 | inputs: [ 241 | { 242 | name: 'proof', 243 | type: 'uint256[]', 244 | internalType: 'uint256[]', 245 | }, 246 | { 247 | name: 'verifyInstance', 248 | type: 'uint256[]', 249 | internalType: 'uint256[]', 250 | }, 251 | { 252 | name: 'aux', 253 | type: 'uint256[]', 254 | internalType: 'uint256[]', 255 | }, 256 | { 257 | name: 'targetInstance', 258 | type: 'uint256[][]', 259 | internalType: 'uint256[][]', 260 | }, 261 | { 262 | name: 'extra', 263 | type: 'uint256[]', 264 | internalType: 'uint256[]', 265 | }, 266 | ], 267 | outputs: [ 268 | { 269 | name: '', 270 | type: 'bytes', 271 | internalType: 'bytes', 272 | }, 273 | ], 274 | stateMutability: 'view', 275 | }, 276 | { 277 | type: 'event', 278 | name: 'OwnershipTransferred', 279 | inputs: [ 280 | { 281 | name: 'previousOwner', 282 | type: 'address', 283 | indexed: true, 284 | internalType: 'address', 285 | }, 286 | { 287 | name: 'newOwner', 288 | type: 'address', 289 | indexed: true, 290 | internalType: 'address', 291 | }, 292 | ], 293 | anonymous: false, 294 | }, 295 | { 296 | type: 'event', 297 | name: 'Revert', 298 | inputs: [ 299 | { 300 | name: 'reason', 301 | type: 'string', 302 | indexed: false, 303 | internalType: 'string', 304 | }, 305 | ], 306 | anonymous: false, 307 | }, 308 | { 309 | type: 'event', 310 | name: 'Trigger', 311 | inputs: [ 312 | { 313 | name: 'sender', 314 | type: 'address', 315 | indexed: false, 316 | internalType: 'address', 317 | }, 318 | { 319 | name: 'data', 320 | type: 'bytes', 321 | indexed: false, 322 | internalType: 'bytes', 323 | }, 324 | ], 325 | anonymous: false, 326 | }, 327 | { 328 | type: 'event', 329 | name: 'UpdateReward', 330 | inputs: [ 331 | { 332 | name: 'oldReward', 333 | type: 'uint256', 334 | indexed: false, 335 | internalType: 'uint256', 336 | }, 337 | { 338 | name: 'newReward', 339 | type: 'uint256', 340 | indexed: false, 341 | internalType: 'uint256', 342 | }, 343 | ], 344 | anonymous: false, 345 | }, 346 | ] 347 | -------------------------------------------------------------------------------- /src/common/api_helper.ts: -------------------------------------------------------------------------------- 1 | import { logReceiptAndEvents } from './log_utils' 2 | import { fromHexString, trimPrefix } from './utils' 3 | import { TxReceipt } from './tx_receipt' 4 | import type { Event } from './event' 5 | 6 | function eventTo7Offsets(event: Event, receiptBaseOffset: number) { 7 | const rst = [event.address_offset[0] + receiptBaseOffset] 8 | 9 | for (let i = 0; i < 4; i++) { 10 | rst.push( 11 | i < event.topics.length 12 | ? (event as any).topics_offset[i][0] + receiptBaseOffset 13 | : 0, 14 | ) 15 | } 16 | 17 | rst.push(event.data_offset[0] + receiptBaseOffset) 18 | rst.push(event.data.length) 19 | return rst 20 | } 21 | 22 | function cleanReceipt(r: string) { 23 | return trimPrefix(trimPrefix(r, '0x'), '02') 24 | } 25 | 26 | export function rlpDecodeAndEventFilter(rawreceiptList: any, srcAddrList: any, srcEsigsList: any) { 27 | const filteredRawReceiptList = [] 28 | const filteredEventsList = [] 29 | 30 | for (const i in rawreceiptList) { 31 | const es = TxReceipt.fromRawStr(rawreceiptList[i]).filter( 32 | srcAddrList, 33 | srcEsigsList, 34 | ) 35 | if (es.length > 0) { 36 | filteredRawReceiptList.push(rawreceiptList[i]) 37 | filteredEventsList.push(es) 38 | } 39 | } 40 | return [filteredRawReceiptList, filteredEventsList] 41 | } 42 | 43 | export function genStreamAndMatchedEventOffsets(rawreceiptList: any[], eventList: any[]): [Uint8Array, any[]] { 44 | let matched_offset_list: any[] = [] 45 | let accumulateReceiptLength = 0 46 | let rawreceipts = '' 47 | 48 | if (rawreceiptList.length !== eventList.length) 49 | throw new Error('rawreceiptList and eventList should have same length.') 50 | 51 | for (const rcpid in rawreceiptList) { 52 | const es = eventList[rcpid] 53 | matched_offset_list = matched_offset_list.concat( 54 | ...es.map((e: Event) => eventTo7Offsets(e, accumulateReceiptLength)), 55 | ) 56 | 57 | const r = cleanReceipt(rawreceiptList[rcpid]) 58 | rawreceipts += r 59 | 60 | accumulateReceiptLength += Math.ceil(r.length / 2) 61 | } 62 | 63 | return [fromHexString(rawreceipts), matched_offset_list] 64 | } 65 | 66 | // Format inputs with length and input value 67 | export function formatIntInput(input: number) { 68 | return `0x${input.toString(16)}:i64 ` 69 | } 70 | 71 | // Format bytes input 72 | export function formatHexStringInput(input: string) { 73 | return `0x${trimPrefix(input, '0x')}:bytes-packed ` 74 | } 75 | 76 | // Format inputs with length and input value 77 | export function formatVarLenInput(input: string) { 78 | // var formatted = ""; 79 | // inputs.map((input) => { 80 | // var inp = trimPrefix(input, '0x') 81 | // formatted += `${formatIntInput(Math.ceil(inp.length / 2))}${formatHexStringInput(inp)}`; 82 | // }); 83 | 84 | const inp = trimPrefix(input, '0x') 85 | const formatted = `${formatIntInput( 86 | Math.ceil(inp.length / 2), 87 | )}${formatHexStringInput(inp)}` 88 | return formatted 89 | } 90 | 91 | export function filterEvents(eventDSAddrList: any[], eventDSEsigsList: any[], rawreceiptList: string | any[]): [Uint8Array, Uint32Array] { 92 | // RLP Decode and Filter 93 | const [filteredRawReceiptList, filteredEventList] = rlpDecodeAndEventFilter( 94 | rawreceiptList, 95 | eventDSAddrList.map(addr => fromHexString(addr)), 96 | eventDSEsigsList.map(esigList => esigList.map((esig: string) => fromHexString(esig))), 97 | ) 98 | 99 | // Gen Offsets 100 | // eslint-disable-next-line prefer-const 101 | let [rawReceipts, _matchedEventOffsets] = genStreamAndMatchedEventOffsets( 102 | filteredRawReceiptList, 103 | filteredEventList, 104 | ) 105 | 106 | // Log 107 | logReceiptAndEvents( 108 | rawreceiptList, 109 | _matchedEventOffsets as any, 110 | filteredEventList, 111 | ) 112 | 113 | // may remove 114 | const matchedEventOffsets = Uint32Array.from(_matchedEventOffsets) as any 115 | 116 | return [rawReceipts, matchedEventOffsets] 117 | } 118 | -------------------------------------------------------------------------------- /src/common/compatible.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { tmpdir } from 'node:os' 4 | import { randomStr } from '@murongg/utils' 5 | 6 | export interface BrowserStreamParam { 7 | fileType?: string 8 | fileName?: string 9 | } 10 | 11 | export interface NodeStreamParam { 12 | tmpPath?: string 13 | } 14 | 15 | export function createFileStream(content: any, param: BrowserStreamParam & NodeStreamParam) { 16 | if (__BROWSER__) { 17 | const { fileType, fileName } = param 18 | if (fileType === undefined || fileName === undefined) 19 | throw new Error(`missing fileType or fileName in createFileStream, given: ${fileType?.toString()} ${fileName?.toString()}`) 20 | 21 | const blob = new Blob([content], { type: fileType }) 22 | return new File([blob], fileName, { type: fileType }) 23 | } 24 | else { 25 | let { tmpPath } = param 26 | if (tmpPath === undefined) 27 | tmpPath = path.join(tmpdir(), `cle_createFileStream_${randomStr()}`) 28 | fs.writeFileSync(tmpPath, content) 29 | return fs.createReadStream(tmpPath) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export { cleContractABI } from './abi/clecontractabi' 2 | 3 | export const networks = [ 4 | { 5 | name: 'Sepolia', 6 | label: 'Sepolia', 7 | value: 11155111, 8 | chainId: 11155111, 9 | expectedEth: 0.002, 10 | hex: '0xaa36a7', 11 | }, 12 | { 13 | name: 'Goerli', 14 | label: 'Goerli', 15 | value: 5, 16 | chainId: 5, 17 | expectedEth: 0.5, 18 | hex: '0x5', 19 | }, 20 | { 21 | name: 'Mainnet', 22 | label: 'Mainnet', 23 | value: 1, 24 | chainId: 1, 25 | }, 26 | ] 27 | 28 | export const AggregatorVerifierABI = { 29 | contractName: 'AggregatorVerifier', 30 | abi: [ 31 | { 32 | inputs: [ 33 | { 34 | internalType: 'contract AggregatorVerifierCoreStep[]', 35 | name: '_steps', 36 | type: 'address[]', 37 | }, 38 | ], 39 | stateMutability: 'nonpayable', 40 | type: 'constructor', 41 | }, 42 | { 43 | inputs: [ 44 | { 45 | internalType: 'uint256[]', 46 | name: 'proof', 47 | type: 'uint256[]', 48 | }, 49 | { 50 | internalType: 'uint256[]', 51 | name: 'verify_instance', 52 | type: 'uint256[]', 53 | }, 54 | { 55 | internalType: 'uint256[]', 56 | name: 'aux', 57 | type: 'uint256[]', 58 | }, 59 | { 60 | internalType: 'uint256[][]', 61 | name: 'target_instance', 62 | type: 'uint256[][]', 63 | }, 64 | ], 65 | name: 'verify', 66 | outputs: [], 67 | stateMutability: 'view', 68 | type: 'function', 69 | constant: true, 70 | }, 71 | ], 72 | } 73 | 74 | export const addressFactory = { 75 | mainnet: 'not support yet', 76 | sepolia: '0x638e019Cfd8A5f1c2fecd473062471c7f6978c9B', 77 | goerli: 'not support yet', 78 | } 79 | export const abiFactory = [ 80 | 'function getAllCLEs() external view returns (address[] memory)', 81 | 'function registry(address destAddr,address bountyToken,uint256 bountyReward,bytes32 dspID,string memory cleURI,uint256 pointX,uint256 pointY) public returns (address cle)', 82 | 'function getCLEBycreator(address creator) external view returns (address[] memory)', 83 | 'function getCLEInfoByAddress(address cle) external view returns (address creator, uint256 bountyReward, address destAddr, string memory cleURI)', 84 | ] 85 | 86 | export const EventSigNewCLE = '0x39f7254e91d2eee9fa8ffc88bc7b0dff5c67916b7a1cc84284b3192bde4ab1d2' 87 | 88 | export const AggregatorVerifierAddress: { [key: string]: { [key: string]: string } } = { 89 | ZKWASMHUB: { 90 | mainnet: 'not support yet', 91 | sepolia: '0xfD74dce645Eb5EB65D818aeC544C72Ba325D93B0', 92 | goerli: '0xbEF9572648284CB63a0DA32a89D3b4F2BeD65a89', 93 | }, 94 | ORA_SINGLE: { 95 | mainnet: '0x9B13520f499e95f7e94E8346Ed8F52D2F830d955', 96 | sepolia: '0x232adE59de1C31bcB48c7c2C45b71805B420b119', 97 | goerli: 'not support yet', 98 | }, 99 | ORA: { 100 | mainnet: 'not support yet', 101 | sepolia: '0xf48dC1e1AaA6bB8cA43b03Ca0695973a2F440090', 102 | goerli: 'not support yet', 103 | }, 104 | } 105 | 106 | export const AddressZero = '0x0000000000000000000000000000000000000000' 107 | 108 | export const DEFAULT_PATH = { 109 | YAML: 'src/cle.yaml', 110 | OUT_WASM: 'build/cle.wasm', 111 | OUT_WAT: 'build/cle.wat', 112 | OUT_INNER_WASM: 'build/inner_pre_pre.wasm', 113 | OUT_INNER_WAT: 'build/inner_pre_pre.wat', 114 | } 115 | 116 | export const DEFAULT_URL = { 117 | PROVER: 'https://rpc.zkwasmhub.com:8090', 118 | ZKWASMHUB: 'https://rpc.zkwasmhub.com:8090', 119 | COMPILER_SERVER: 'https://compiler.ora.io/compile', 120 | PINATA: 'https://api.pinata.cloud/pinning/pinFileToIPFS', 121 | } 122 | 123 | export const DEFAULT_CIRCUIT_SIZE = 22 124 | export const MIN_CIRCUIT_SIZE = 18 125 | export const MAX_CIRCUIT_SIZE = 24 126 | 127 | export const PROVER_RPC_CONSTANTS = { 128 | TASK_STATUS_DONE: 'Done', 129 | TASK_STATUS_FAIL: 'Fail', 130 | TASK_STATUS_DRYRUNFAILED: 'DryRunFailed', 131 | TASK_STATUS_SETUP_FINISH_LIST: ['Done', 'Fail'], 132 | TASK_STATUS_PROVE_FINISH_LIST: ['Done', 'Fail', 'DryRunFailed'], 133 | IMAGE_STATUS_VALID_LIST: ['Verified', 'Initialized'], 134 | TASK_TYPE_SETUP: 'Setup', 135 | TASK_TYPE_RESET: 'Reset', 136 | } 137 | 138 | // TODO: compatible only, deprecating 139 | export const FinishStatusList = PROVER_RPC_CONSTANTS.TASK_STATUS_PROVE_FINISH_LIST 140 | -------------------------------------------------------------------------------- /src/common/error.ts: -------------------------------------------------------------------------------- 1 | export class CLERequireFailed extends Error { 2 | constructor(message: string | undefined) { 3 | super(message) 4 | } 5 | } 6 | 7 | export class ImageAlreadyExists extends Error { 8 | constructor(message: string | undefined) { 9 | super(message) 10 | } 11 | } 12 | 13 | export class ImageNotExists extends Error { 14 | constructor(message: string | undefined) { 15 | super(message) 16 | } 17 | } 18 | 19 | export class ImageInvalid extends Error { 20 | constructor(message: string | undefined) { 21 | super(message) 22 | } 23 | } 24 | 25 | export class ProveTaskNotReady extends Error { 26 | constructor(message: string | undefined) { 27 | super(message) 28 | } 29 | } 30 | 31 | export class PaymentError extends Error { 32 | constructor(message: string | undefined) { 33 | super(message) 34 | } 35 | } 36 | 37 | export class YamlInvalidFormat extends Error { 38 | constructor(message: string | undefined) { 39 | super(message) 40 | } 41 | } 42 | export class YamlHealthCheckFailed extends Error { 43 | constructor(message: string | undefined) { 44 | super(message) 45 | } 46 | } 47 | 48 | export class YamlNotSupported extends Error { 49 | constructor(message: string | undefined) { 50 | super(message) 51 | } 52 | } 53 | 54 | export class TDNoTaskFound extends Error { 55 | constructor(message: string | undefined) { 56 | super(message) 57 | } 58 | } 59 | 60 | // Publish Error 61 | export class CLEAlreadyExist extends Error { 62 | constructor(message: string | undefined) { 63 | super(message) 64 | } 65 | } 66 | 67 | export class TxFailed extends Error { 68 | constructor(message: string | undefined) { 69 | super(message) 70 | } 71 | } 72 | 73 | export class CLEAddressMissing extends Error { 74 | constructor(message: string | undefined) { 75 | super(message) 76 | } 77 | } 78 | 79 | export class DSPNotFound extends Error { 80 | constructor(message: string | undefined) { 81 | super(message) 82 | } 83 | } 84 | 85 | export class BlockNotFound extends Error { 86 | constructor(message: string | undefined) { 87 | super(message) 88 | } 89 | } 90 | 91 | export class OldBlockNumber extends Error { 92 | constructor(message: string | undefined) { 93 | super(message) 94 | } 95 | } 96 | 97 | export class InsufficientBalance extends Error { 98 | constructor(message: string | undefined) { 99 | super(message) 100 | } 101 | } 102 | export class UploadFileNotExist extends Error { 103 | constructor(message: string | undefined) { 104 | super(message) 105 | } 106 | } 107 | 108 | export class CircuitSizeOutOfRange extends Error { 109 | constructor(message: string | undefined) { 110 | super(message) 111 | } 112 | } 113 | 114 | export class BatchStyleUnsupport extends Error { 115 | constructor(message: string | undefined) { 116 | super(message) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/common/event.ts: -------------------------------------------------------------------------------- 1 | // import type { Decoded } from './rlp' 2 | import { logger } from './logger' 3 | import { areEqualArrays, toHexString } from './utils' 4 | export class Event { 5 | address: Uint8Array 6 | topics: Uint8Array[] 7 | data: Uint8Array 8 | address_offset: number[] 9 | topics_offset: number[] 10 | data_offset: number[] 11 | constructor( 12 | address: Uint8Array, 13 | topics: Uint8Array[], 14 | data: Uint8Array, 15 | address_offset: number[], 16 | topics_offset: any[], 17 | data_offset: number[], 18 | ) { 19 | this.address = address 20 | this.topics = topics 21 | this.data = data 22 | this.address_offset = address_offset 23 | this.topics_offset = topics_offset 24 | this.data_offset = data_offset 25 | } 26 | 27 | prettyPrint(prefix = '', withoffsets = true) { 28 | logger.log( 29 | prefix, 30 | '|--addr :', 31 | toHexString(this.address), 32 | withoffsets ? this.address_offset : '', 33 | ) 34 | for (let j = 0; j < this.topics.length; j++) { 35 | logger.log( 36 | prefix, 37 | `|--arg#${j.toString()}: ${toHexString(this.topics[j])}`, 38 | withoffsets ? this.topics_offset[j] : '', 39 | ) 40 | } 41 | logger.log( 42 | prefix, 43 | '|--data :', 44 | toHexString(this.data), 45 | withoffsets ? this.data_offset : '', 46 | ) 47 | logger.log('') 48 | } 49 | 50 | match(wantedAddressList: string | any[], wantedEsigsList: string | any[]) { 51 | if (wantedAddressList.length !== wantedEsigsList.length) 52 | throw new Error('[-] source address list length != source event signature list length.') 53 | 54 | for (let i = 0; i < wantedAddressList.length; i++) { 55 | if (areEqualArrays(this.address, wantedAddressList[i])) { 56 | const esig = this.topics[0] 57 | const wantedEsigs = wantedEsigsList[i] 58 | for (let j = 0; j < wantedEsigs.length; j++) { 59 | if (areEqualArrays(esig, wantedEsigs[j])) 60 | return true 61 | } 62 | } 63 | } 64 | return false 65 | } 66 | 67 | match_one(wantedAddress: Uint8Array, wantedEsigs: string | any[]) { 68 | if (areEqualArrays(this.address, wantedAddress)) { 69 | const esig = this.topics[0] 70 | for (let j = 0; j < wantedEsigs.length; j++) { 71 | if (areEqualArrays(esig, wantedEsigs[j])) { 72 | // TODO: what this variable is used for? 73 | // rst.push(this) 74 | break 75 | } 76 | } 77 | } 78 | } 79 | 80 | // TODO: must be match types and handle edge cases 81 | static fromRlp(rlpdata: any) { 82 | const address = rlpdata[0].data 83 | const address_offset = rlpdata[0].dataIndexes 84 | 85 | const topics = [] 86 | const topics_offset = [] 87 | for (let i = 0; i < rlpdata[1].data.length; i++) { 88 | topics.push(rlpdata[1].data[i].data) 89 | topics_offset.push(rlpdata[1].data[i].dataIndexes) 90 | } 91 | 92 | const data = rlpdata[2].data 93 | const data_offset = rlpdata[2].dataIndexes 94 | 95 | return new Event( 96 | address, 97 | topics, 98 | data, 99 | address_offset, 100 | topics_offset, 101 | data_offset, 102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export { getRawReceipts, getBlockByNumber } from './ethers_helper' 2 | export * as Error from './error' 3 | export * as constants from './constants' 4 | export * as utils from './utils' 5 | export * from './logger' 6 | -------------------------------------------------------------------------------- /src/common/log_utils.ts: -------------------------------------------------------------------------------- 1 | import { logger } from './logger' 2 | 3 | export function currentNpmScriptName() { 4 | return process.env.npm_lifecycle_event 5 | } 6 | 7 | export function logDivider() { 8 | const line = '='.repeat(process.stdout.columns) 9 | logger.log(line) 10 | } 11 | 12 | export function logReceiptAndEvents( 13 | rawreceiptList: string | any[], 14 | // blockid, 15 | matchedEventOffsets: string | any[], 16 | filteredEventList: any, 17 | ) { 18 | logger.log( 19 | '[*] ', 20 | rawreceiptList.length, 21 | rawreceiptList.length > 1 22 | ? 'receipts fetched' 23 | : 'receipt fetched', 24 | ) 25 | logger.log( 26 | '[*] ', 27 | matchedEventOffsets.length / 7, 28 | matchedEventOffsets.length / 7 > 1 ? ' events matched' : ' event matched', 29 | ) 30 | for (const i in filteredEventList) { 31 | for (const j in filteredEventList[i]) { 32 | filteredEventList[i][j].prettyPrint( 33 | `\tTx[${i}]Event[${j}]`, 34 | false, 35 | ) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import type { CLELogger } from 'zkwasm-toolchain' 2 | import { logger, setCLELogger } from 'zkwasm-toolchain' 3 | 4 | export class SilentLogger implements CLELogger { 5 | debug(..._args: any[]): void {} 6 | info(..._args: any[]): void {} 7 | warn(..._args: any[]): void {} 8 | error(..._args: any[]): void {} 9 | log(..._args: any[]): void {} 10 | write(_msg: string): void {} 11 | } 12 | 13 | export { 14 | logger, 15 | setCLELogger, 16 | CLELogger, 17 | } 18 | -------------------------------------------------------------------------------- /src/common/rlp.ts: -------------------------------------------------------------------------------- 1 | export function encode(input: string) { 2 | if (Array.isArray(input)) { 3 | const output = [] 4 | for (let i = 0; i < input.length; i++) 5 | output.push(encode(input[i])) 6 | 7 | const buf = concatBytes(...output) 8 | return concatBytes(encodeLength(buf.length, 192), buf) 9 | } 10 | const inputBuf = toBytes(input) 11 | if (inputBuf.length === 1 && inputBuf[0] < 128) 12 | return inputBuf 13 | 14 | return concatBytes(encodeLength(inputBuf.length, 128), inputBuf) 15 | } 16 | 17 | function decodeLength(v: Uint8Array) { 18 | if (v[0] === 0) 19 | throw new Error('invalid RLP: extra zeros') 20 | 21 | return parseHexByte(bytesToHex(v)) 22 | } 23 | 24 | function encodeLength(len: number, offset: number) { 25 | if (len < 56) 26 | return Uint8Array.from([len + offset]) 27 | 28 | const hexLength = numberToHex(len) 29 | const lLength = hexLength.length / 2 30 | const firstByte = numberToHex(offset + 55 + lLength) 31 | return Uint8Array.from(hexToBytes(firstByte + hexLength)) 32 | } 33 | 34 | export interface Decoded { 35 | data: Uint8Array | Decoded[] | Decoded 36 | dataIndexes: number[] 37 | isList: boolean 38 | } 39 | 40 | export function decode(input: any, stream?: false): Decoded[] 41 | export function decode(input: any, stream?: true): Decoded | Uint8Array 42 | export function decode(input: any, stream: false | true = false): Decoded[] | Decoded | Uint8Array { 43 | if (!input || input.length === 0) 44 | return Uint8Array.from([]) 45 | 46 | const inputBytes = toBytes(input) 47 | const decoded = _decode(inputBytes, 0) 48 | 49 | if (stream) 50 | return decoded 51 | 52 | return decoded.data 53 | } 54 | 55 | function _decode(input: Uint8Array, start: number): Decoded { 56 | let length, llength, data 57 | const firstByte = input[start] 58 | 59 | if (firstByte <= 0x7F) { 60 | // SINGLE_CHAR 61 | return { 62 | data: input.slice(start, start + 1), 63 | dataIndexes: [start, start], 64 | isList: false, 65 | } 66 | } 67 | else if (firstByte <= 0xB7) { 68 | // SHORT_STRING 69 | length = firstByte - 0x80 70 | if (firstByte === 0x80) 71 | data = Uint8Array.from([]) // empty string 72 | 73 | else 74 | data = input.slice(start + 1, start + 1 + length) 75 | 76 | if (length === 2 && data[start] < 0x80) { 77 | throw new Error( 78 | 'invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed', 79 | ) 80 | } 81 | 82 | return { 83 | data, 84 | dataIndexes: [start + 1, start + length], 85 | isList: false, 86 | } 87 | } 88 | else if (firstByte <= 0xBF) { 89 | // LONG_STRING 90 | llength = firstByte - 0xB7 91 | if (input.length - start - 1 < llength) 92 | throw new Error('invalid RLP: not enough bytes for string length') 93 | 94 | length = decodeLength(input.slice(start + 1, start + 1 + llength)) 95 | if (length <= 55) { 96 | throw new Error( 97 | 'invalid RLP: expected string length to be greater than 55', 98 | ) 99 | } 100 | data = input.slice(start + 1 + llength, start + 1 + length + llength) 101 | 102 | return { 103 | data, 104 | dataIndexes: [start + 1 + llength, start + length + llength], 105 | isList: false, 106 | } 107 | } 108 | else if (firstByte <= 0xF7) { 109 | // SHORT_LIST 110 | length = firstByte - 0xC0 111 | return { 112 | data: _decodeList(input, start + 1, start + length), 113 | dataIndexes: [start + 1, start + length], 114 | isList: true, 115 | } 116 | } 117 | else { 118 | // LONG_LIST 119 | llength = firstByte - 0xF7 120 | length = decodeLength(input.slice(start + 1, start + 1 + llength)) 121 | if (length < 56) 122 | throw new Error('invalid RLP: encoded list too short') 123 | 124 | const totalLength = llength + length 125 | if (start + totalLength > input.length) 126 | throw new Error('invalid RLP: total length is larger than the data') 127 | 128 | return { 129 | data: _decodeList(input, start + llength + 1, start + length + llength), 130 | dataIndexes: [start + llength + 1, start + totalLength], 131 | isList: true, 132 | } 133 | } 134 | } 135 | 136 | function _decodeList(input: Uint8Array, start: number, end: number) { 137 | let startIdx = start 138 | const decoded = [] 139 | while (startIdx <= end) { 140 | const d = _decode(input, startIdx) 141 | decoded.push(d) 142 | startIdx = d.dataIndexes[1] + 1 143 | } 144 | if (startIdx !== end + 1) 145 | throw new Error('invalid RLP: decode list input invalid') 146 | 147 | return decoded 148 | } 149 | 150 | const cachedHexes = Array.from({ length: 256 }, (_v, i) => { 151 | return i.toString(16).padStart(2, '0') 152 | }) 153 | 154 | function bytesToHex(uint8a: Uint8Array) { 155 | let hex = '' 156 | for (let i = 0; i < uint8a.length; i++) 157 | hex += cachedHexes[uint8a[i]] 158 | 159 | return hex 160 | } 161 | 162 | function parseHexByte(hexByte: string) { 163 | const byte = Number.parseInt(hexByte, 16) 164 | if (Number.isNaN(byte)) 165 | throw new Error('Invalid byte sequence') 166 | return byte 167 | } 168 | 169 | function hexToBytes(hex: string) { 170 | if (typeof hex !== 'string') 171 | throw new TypeError(`hexToBytes: expected string, got ${typeof hex}`) 172 | 173 | if (hex.length % 2) 174 | throw new Error('hexToBytes: received invalid unpadded hex') 175 | const array = new Uint8Array(hex.length / 2) 176 | for (let i = 0; i < array.length; i++) { 177 | const j = i * 2 178 | array[i] = parseHexByte(hex.slice(j, j + 2)) 179 | } 180 | return array 181 | } 182 | 183 | function concatBytes(...arrays: Uint8Array[]) { 184 | if (arrays.length === 1) 185 | return arrays[0] 186 | const length = arrays.reduce((a, arr) => a + arr.length, 0) 187 | const result = new Uint8Array(length) 188 | for (let i = 0, pad = 0; i < arrays.length; i++) { 189 | const arr = arrays[i] 190 | result.set(arr, pad) 191 | pad += arr.length 192 | } 193 | return result 194 | } 195 | 196 | function utf8ToBytes(utf?: string | undefined) { 197 | return new TextEncoder().encode(utf) 198 | } 199 | 200 | function numberToHex(integer: number | bigint) { 201 | if (integer < 0) 202 | throw new Error('Invalid integer as argument, must be unsigned!') 203 | 204 | const hex = integer.toString(16) 205 | return hex.length % 2 ? `0${hex}` : hex 206 | } 207 | 208 | function padToEven(a: string) { 209 | return a.length % 2 ? `0${a}` : a 210 | } 211 | 212 | function isHexPrefixed(str: string) { 213 | return str.length >= 2 && str[0] === '0' && str[1] === 'x' 214 | } 215 | 216 | function stripHexPrefix(str: string) { 217 | if (typeof str !== 'string') 218 | return str 219 | 220 | return isHexPrefixed(str) ? str.slice(2) : str 221 | } 222 | 223 | function toBytes(v: Uint8Array | string | number | bigint | null | undefined) { 224 | if (v instanceof Uint8Array) 225 | return v 226 | 227 | if (typeof v === 'string') { 228 | if (isHexPrefixed(v)) 229 | return hexToBytes(padToEven(stripHexPrefix(v))) 230 | 231 | return utf8ToBytes(v) 232 | } 233 | if (typeof v === 'number' || typeof v === 'bigint') { 234 | if (!v) 235 | return Uint8Array.from([]) 236 | 237 | return hexToBytes(numberToHex(v)) 238 | } 239 | if (v === null || v === undefined) 240 | return Uint8Array.from([]) 241 | 242 | throw new Error(`toBytes: received unsupported type ${typeof v}`) 243 | } 244 | 245 | const utils = { 246 | bytesToHex, 247 | concatBytes, 248 | hexToBytes, 249 | utf8ToBytes, 250 | } 251 | 252 | const RLP = { encode, decode, ...utils } 253 | export default RLP 254 | -------------------------------------------------------------------------------- /src/common/tx_receipt.ts: -------------------------------------------------------------------------------- 1 | import { fromHexString } from './utils' 2 | import { Event } from './event' 3 | 4 | import type { Decoded } from './rlp' 5 | import RLP from './rlp' 6 | 7 | export class TxReceipt { 8 | status: any 9 | gasUsed: any 10 | logsBloom: any 11 | events: any 12 | constructor(status: number, gasUsed: number, logsBloom: Uint8Array, events: Event[]) { 13 | this.status = status 14 | this.gasUsed = gasUsed 15 | this.logsBloom = logsBloom 16 | this.events = events 17 | } 18 | 19 | static fromRawBin(rawReceipt: Uint8Array) { 20 | /** EIP-2718 */ 21 | if (rawReceipt[0] <= 2) { 22 | // const txtype = rawReceipt[0] // useless 23 | rawReceipt = rawReceipt.slice(1) 24 | } 25 | const rlpdata = RLP.decode(rawReceipt) 26 | const status = (rlpdata[0].data as Uint8Array)[0] 27 | const gasUsed = (rlpdata[1].data as Uint8Array)[0] 28 | const logsBloom = (rlpdata[2].data as Uint8Array) 29 | 30 | const rlpevents = rlpdata[3].data as Decoded[] 31 | const events = [] 32 | for (let i = 0; i < rlpevents.length; i++) 33 | events.push(Event.fromRlp(rlpevents[i].data)) 34 | 35 | return new TxReceipt(status, gasUsed, logsBloom, events) 36 | } 37 | 38 | static fromRawStr(rawReceiptStr: string) { 39 | return TxReceipt.fromRawBin(fromHexString(rawReceiptStr)) 40 | } 41 | 42 | toValidEvents() { 43 | if (this.status !== 0x1) { 44 | // tx failed 45 | return [] 46 | } 47 | else { 48 | return this.events 49 | } 50 | } 51 | 52 | filter(wantedAddressList: any, wantedEsigsList: any) { 53 | const events = this.toValidEvents() 54 | const rst = [] 55 | for (let i = 0; i < events.length; i++) { 56 | if (events[i].match(wantedAddressList, wantedEsigsList)) 57 | rst.push(events[i]) 58 | // TODO: double check: what if there's more than 1 events matched? 59 | // break; 60 | } 61 | return rst 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ddp/ethereum/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 3 | import type { KeyofToArray } from '@murongg/utils/index' 4 | import { cleContractABI } from '../../common/constants' 5 | import type { ProofParams } from '../../types' 6 | import { DataDestinationPlugin } from '../interface' 7 | import { logger } from '../../common' 8 | 9 | export interface EthereumDDPGoParams { 10 | signer: ethers.Wallet 11 | gasLimit: number 12 | onlyMock: boolean 13 | } 14 | 15 | export class EthereumDataDestinationPlugin extends DataDestinationPlugin { 16 | goParams: KeyofToArray = ['signer', 'gasLimit'] 17 | 18 | async go(cleId: string, proofParams: ProofParams, goParams: EthereumDDPGoParams): Promise { 19 | const proof = ZkWasmUtil.bytesToBigIntArray(proofParams.aggregate_proof) 20 | const instances = ZkWasmUtil.bytesToBigIntArray(proofParams.batch_instances) 21 | const aux = ZkWasmUtil.bytesToBigIntArray(proofParams.aux) 22 | // const arg = ZkWasmUtil.bytesToBigIntArray(proofParams.instances) 23 | const arg = proofParams.instances.map((ins) => { return ZkWasmUtil.bytesToBigIntArray(ins) }) 24 | // const arg = decode2DProofParam(proofParams.instances) 25 | const extra = proofParams.extra ? ZkWasmUtil.bytesToBigIntArray(proofParams.extra) : [0n] 26 | // TODO: double check: decoded extra should be uint256[blocknum1, blocknum2] 27 | 28 | // try { 29 | const cleSC = new ethers.Contract(cleId, cleContractABI, goParams.signer) 30 | 31 | const callparams = [proof, instances, aux, arg, extra, { gasLimit: goParams.gasLimit }] 32 | 33 | // throw err if execution revert 34 | await cleSC.callStatic.trigger(...callparams) 35 | 36 | if (goParams.onlyMock) 37 | return 38 | 39 | // send tx if passed local test 40 | const tx = await cleSC.trigger(...callparams) 41 | 42 | // logger.info("transaction submitted, tx hash: " + tx.hash); 43 | logger.log(`transaction submitted, tx hash: ${tx.hash}`) 44 | const receipt = await tx.wait() 45 | // await tx.wait() 46 | // logger.info("transaction confirmed, block number: " + receipt.blockNumber); 47 | logger.log(`transaction confirmed, block number: ${receipt.blockNumber}`) 48 | // this.status == 'off'; 49 | // this.senderIdx = (this.senderIdx + 1) % config.UserPrivateKey.length; 50 | // } 51 | // catch (err) { 52 | // logger.error(`failed to trigger for graph ${taskDetails.zkgid}`); 53 | // logger.error(err); 54 | // console.error(err) 55 | // this.status == 'off'; 56 | // this.senderIdx = (this.senderIdx + 1) % config.UserPrivateKey.length; 57 | // } 58 | } 59 | } 60 | 61 | // const provider = new ethers.providers.JsonRpcProvider(); 62 | // const signer = new ethers.Wallet(config.UserPrivateKey[this.senderIdx], provider); 63 | -------------------------------------------------------------------------------- /src/ddp/hub.ts: -------------------------------------------------------------------------------- 1 | import type { CLEYaml } from '../types' 2 | import { EthereumDataDestinationPlugin } from './ethereum' 3 | import type { DataDestinationPlugin } from './interface' 4 | 5 | export interface DDPHubForeignKeys { 6 | // isLocal?: boolean 7 | } 8 | 9 | export class DDPHub { 10 | hub: Map> 11 | 12 | constructor() { 13 | this.hub = new Map>() 14 | } 15 | 16 | /** 17 | * @param {string} primaryKey yaml.dataSources[i].kind 18 | * @param {object} foreignKeys e.g. {"key": value} 19 | * @returns Combined Key String: Better to be human readable 20 | */ 21 | toHubKey(primaryKey: string, _foreignKeys: DDPHubForeignKeys) { 22 | return primaryKey 23 | // const { isLocal } = foreignKeys 24 | // const keyFullLocal = (!isLocal || isLocal == null) ? 'full' : 'local' 25 | // return `${primaryKey}:${keyFullLocal}` 26 | } 27 | 28 | toPrimaryKey(sigKeys: string[]): string { 29 | return sigKeys.join('.') 30 | } 31 | 32 | // diff from DSPHub 33 | // toHubKeyList(sigKeysList: any[][], foreignKeys: DDPHubForeignKeys): string[] { 34 | // return sigKeysList.map(sigKeys => { 35 | // let primaryKey = this.toPrimaryKey(sigKeys) 36 | // return this.toHubKey(primaryKey, foreignKeys) 37 | // }); 38 | // } 39 | 40 | // diff from DSPHub 41 | toPrimaryKeyList(sigKeysList: any[][], _foreignKeys: DDPHubForeignKeys): string[] { 42 | return sigKeysList.map((sigKeys) => { 43 | return this.toPrimaryKey(sigKeys) 44 | }) 45 | } 46 | 47 | toHubKeyListByYaml(cleYaml: CLEYaml, foreignKeys: DDPHubForeignKeys): string[] { 48 | const sigKeysList = cleYaml.getSignificantKeys(false) 49 | const primaryKeyList = this.toPrimaryKeyList(sigKeysList, foreignKeys) 50 | return primaryKeyList.map((primaryKey) => { 51 | return this.toHubKey(primaryKey, foreignKeys) 52 | }) 53 | } 54 | 55 | setDDP(primaryKey: string, foreignKeys: DDPHubForeignKeys, ddp: InstanceType>) { 56 | this.hub.set(this.toHubKey(primaryKey, foreignKeys), ddp) 57 | } 58 | 59 | getDDP(primaryKey: string, foreignKeys: DDPHubForeignKeys): InstanceType { 60 | const key = this.toHubKey(primaryKey, foreignKeys) 61 | if (!this.hub.has(key)) 62 | throw new Error(`Data Destination Plugin Hub Key "${key}" doesn't exist.`) 63 | const ddp = this.hub.get(key) 64 | if (ddp === undefined) 65 | throw new Error('Impossible') 66 | return ddp 67 | } 68 | 69 | getDDPsByYaml(cleYaml: CLEYaml, foreignKeys: DDPHubForeignKeys = {}): InstanceType[] { 70 | const sigKeysList = cleYaml.getSignificantKeys(true) 71 | const primaryKeyList = this.toPrimaryKeyList(sigKeysList, foreignKeys) 72 | return primaryKeyList.map((primaryKey) => { 73 | const ddp = this.getDDP(primaryKey, foreignKeys) 74 | return ddp 75 | }) 76 | } 77 | 78 | initialize(): void { 79 | /** 80 | * Register DDPs 81 | */ 82 | this.setDDP('ethereum', {}, new EthereumDataDestinationPlugin()) 83 | } 84 | } 85 | 86 | /** 87 | * Global DDP Hub 88 | */ 89 | export const ddpHub = new DDPHub() 90 | ddpHub.initialize() 91 | -------------------------------------------------------------------------------- /src/ddp/interface.ts: -------------------------------------------------------------------------------- 1 | import type { KeyofToArray } from '@murongg/utils/index' 2 | import type { ProofParams } from '../types' 3 | import { paramsNormalize } from '../common/utils' 4 | 5 | export abstract class DataDestinationPlugin { 6 | abstract goParams: KeyofToArray 7 | toGoParams(params: Record) { 8 | return paramsNormalize(this.goParams as string[], params) as GP 9 | } 10 | abstract go(cleId: string, proofParams: ProofParams, goParams: GP): Promise 11 | } 12 | -------------------------------------------------------------------------------- /src/dsp/ethereum-offchain.bytes/index.ts: -------------------------------------------------------------------------------- 1 | import type { KeyofToArray } from '@murongg/utils/index' 2 | import type { providers } from 'ethers' 3 | import type { Input } from 'zkwasm-toolchain' 4 | import { DataPrep, DataSourcePlugin } from '../interface' 5 | 6 | // reuse ethereum dsp for blocks 7 | import { fillInputBlocks } from '../ethereum/fill_blocks' 8 | import { prepareBlocksByYaml } from '../ethereum/prepare_blocks' 9 | 10 | import { trimPrefix } from '../../common/utils' 11 | import type { CLEYaml } from '../../types' 12 | import type { BlockPrep } from '../ethereum/blockprep' 13 | import { dspHooks } from '../hooks' 14 | 15 | export interface EthereumOffchainDPDataPrep { 16 | blockPrepMap: Map 17 | blocknumberOrder: any[] 18 | contextBlocknumber: number 19 | offchainData: any 20 | expectedStateStr: string 21 | } 22 | 23 | export interface EthereumOffchainDSPPrepareParams { 24 | provider: providers.JsonRpcProvider 25 | contextBlocknumber: number 26 | // contextBlockhash: string 27 | offchainData: any 28 | expectedStateStr: string 29 | } 30 | export interface EthereumOffchainDSPExecParams { 31 | provider: providers.JsonRpcProvider 32 | blockId: string 33 | offchainData: any 34 | } 35 | 36 | export interface EthereumOffchainDSPProveParams { 37 | provider: providers.JsonRpcProvider 38 | blockId: string 39 | offchainData: any 40 | expectedStateStr: string 41 | } 42 | 43 | export class EthereumOffchainDP extends DataPrep { 44 | blockPrepMap: any 45 | blocknumberOrder: any 46 | contextBlocknumber: any 47 | offchainData: any 48 | expectedStateStr: any 49 | constructor(blockPrepMap: any, blocknumberOrder: any, contextBlocknumber: any, offchainData: any, expectedStateStr: any) { 50 | super(expectedStateStr) 51 | this.blockPrepMap = blockPrepMap 52 | this.blocknumberOrder = blocknumberOrder 53 | this.contextBlocknumber = contextBlocknumber 54 | this.offchainData = offchainData 55 | this.expectedStateStr = expectedStateStr 56 | } 57 | } 58 | 59 | export class EthereumOffchainDSP extends DataSourcePlugin { 60 | // SHOULD align with cle-lib/dsp/ 61 | getLibDSPName() { return 'ethereum-offchain.bytes' } 62 | 63 | async prepareData(cleYaml: CLEYaml, prepareParams: EthereumOffchainDSPPrepareParams) { 64 | const { provider, contextBlocknumber, offchainData, expectedStateStr } = prepareParams 65 | const ethDP = await prepareBlocksByYaml(provider, contextBlocknumber, expectedStateStr || '', cleYaml) 66 | return new EthereumOffchainDP( 67 | ethDP.blockPrepMap, 68 | ethDP.blocknumberOrder, 69 | ethDP.contextBlocknumber, 70 | // add offchain data 71 | offchainData, 72 | ethDP.expectedStateStr, 73 | ) 74 | } 75 | 76 | fillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: EthereumOffchainDPDataPrep) { 77 | input = fillInputBlocks(input, cleYaml, dataPrep.blockPrepMap, dataPrep.blocknumberOrder, dataPrep.contextBlocknumber) 78 | // add offchain data 79 | input.addVarLenHexString(dataPrep.offchainData) 80 | return input 81 | } 82 | 83 | fillProveInput(input: any, cleYaml: CLEYaml, dataPrep: EthereumOffchainDPDataPrep) { 84 | this.fillExecInput(input, cleYaml, dataPrep) 85 | // add offchain data 86 | input.addVarLenHexString(dataPrep.offchainData) 87 | // add expected State Str 88 | const expectedStateStr = trimPrefix(dataPrep.expectedStateStr, '0x') 89 | input.addVarLenHexString(expectedStateStr, true) 90 | return input 91 | } 92 | 93 | // TODO: copy instead of rename 94 | toProveDataPrep(execDataPrep: EthereumOffchainDPDataPrep, execResult: string) { 95 | const proveDataPrep = execDataPrep 96 | proveDataPrep.expectedStateStr = execResult 97 | return proveDataPrep 98 | } 99 | 100 | execParams: KeyofToArray = ['blockId', 'offchainData'] 101 | proveParams: KeyofToArray = ['blockId', 'offchainData', 'expectedStateStr'] 102 | 103 | async toPrepareParams(params: EthereumOffchainDSPExecParams, type: 'exec'): Promise 104 | async toPrepareParams(params: EthereumOffchainDSPProveParams, type: 'prove'): Promise 105 | async toPrepareParams(params: EthereumOffchainDSPExecParams | EthereumOffchainDSPProveParams, type: 'exec' | 'prove'): Promise { 106 | let expectedStateStr = '' 107 | const { provider, blockId, offchainData } = params 108 | if (type === 'prove') 109 | expectedStateStr = (params as EthereumOffchainDSPProveParams).expectedStateStr || '' 110 | 111 | // Get block 112 | // TODO: optimize: no need to getblock if blockId is block num 113 | const rawblock = await dspHooks.getBlock(provider, blockId) 114 | const blockNumber = parseInt(rawblock.number) 115 | // const blockHash = rawblock.hash 116 | 117 | return { 118 | provider, 119 | contextBlocknumber: blockNumber, 120 | // contextBlockhash: '-deprecate-', 121 | // add offchain data 122 | offchainData, 123 | expectedStateStr, 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe-ethereum/dataprep.ts: -------------------------------------------------------------------------------- 1 | import type { EthereumDataPrep } from '../ethereum/blockprep' 2 | import type { UnsafeEthereumDataPrep } from '../ethereum.unsafe/dataprep' 3 | import { DataPrep } from '../interface' 4 | 5 | // includes both exec & prove params 6 | export class UnsafeSafeETHDP extends DataPrep { 7 | unsafeETHDP: UnsafeEthereumDataPrep 8 | safeEthDP: EthereumDataPrep 9 | // contextBlocknumber & expectedStateStr should use these, not the ones in 2 xxxDataPreps 10 | contextBlocknumber: number 11 | latestBlocknumber: number 12 | constructor(ethUnsafeDP: UnsafeEthereumDataPrep, ethDP: EthereumDataPrep, contextBlocknumber: number, expectedStateStr: string, latestBlocknumber: number) { 13 | super(expectedStateStr) 14 | this.unsafeETHDP = ethUnsafeDP 15 | this.safeEthDP = ethDP 16 | this.contextBlocknumber = contextBlocknumber 17 | this.latestBlocknumber = latestBlocknumber 18 | 19 | // unify to avoid ambiguity, useless 20 | this.unsafeETHDP.contextBlocknumber = contextBlocknumber 21 | this.unsafeETHDP.expectedStateStr = expectedStateStr 22 | this.unsafeETHDP.latestBlocknumber = latestBlocknumber 23 | this.safeEthDP.contextBlocknumber = contextBlocknumber 24 | this.safeEthDP.expectedStateStr = expectedStateStr 25 | this.safeEthDP.latestBlocknumber = latestBlocknumber 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe-ethereum/index.ts: -------------------------------------------------------------------------------- 1 | import type { Input } from 'zkwasm-toolchain' 2 | import type { CLEYaml } from '../../types' 3 | import { fillInputBlocksWithoutLatestBlockhash, fillInputEvents, setFillInputEventsFunc } from '../ethereum/fill_blocks' 4 | import { unsafePrepareData } from '../ethereum.unsafe' 5 | import { ExtendableEthereumDataSourcePlugin, safePrepareData } from '../ethereum' 6 | import { unsafeFillInputEvents } from '../ethereum.unsafe/fill' 7 | import { dspHooks } from '../hooks' 8 | import { genAuxParams } from '../ethereum/aux' 9 | import { UnsafeSafeETHDP } from './dataprep' 10 | 11 | export class UnsafeSafeETHDSP extends ExtendableEthereumDataSourcePlugin { 12 | constructor() { 13 | super() 14 | } 15 | 16 | // SHOULD align with cle-lib/dsp/ 17 | // TODO unsafe 18 | getLibDSPName() { return 'ethereum.unsafe-ethereum' } 19 | 20 | async prepareData(cleYaml: CLEYaml, prepareParams: Record) { 21 | const { provider, contextBlocknumber, expectedStateStr } = prepareParams 22 | const unsafeEthDP = await unsafePrepareData(cleYaml, prepareParams) 23 | const safeEthDP = await safePrepareData(cleYaml, prepareParams) 24 | const latestBlocknumber = await dspHooks.getBlockNumber(provider) // used to decide recent blocks / bho blocks 25 | const dataPrep = new UnsafeSafeETHDP(unsafeEthDP, safeEthDP, contextBlocknumber, expectedStateStr, latestBlocknumber) 26 | return dataPrep 27 | } 28 | 29 | fillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: UnsafeSafeETHDP) { 30 | // append unsafe input 31 | setFillInputEventsFunc(unsafeFillInputEvents) 32 | input = fillInputBlocksWithoutLatestBlockhash(input, cleYaml, dataPrep.unsafeETHDP.blockPrepMap, dataPrep.unsafeETHDP.blocknumberOrder) 33 | 34 | // append safe input 35 | setFillInputEventsFunc(fillInputEvents) 36 | input = fillInputBlocksWithoutLatestBlockhash(input, cleYaml, dataPrep.safeEthDP.blockPrepMap, dataPrep.safeEthDP.blocknumberOrder) 37 | 38 | // add aux params, only for safe mode 39 | input.auxParams = genAuxParams(cleYaml, dataPrep.safeEthDP) 40 | 41 | input.addInt(dataPrep.contextBlocknumber, 1) 42 | return input 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe/dataprep.ts: -------------------------------------------------------------------------------- 1 | import { BlockPrep, EthereumDataPrep } from '../ethereum/blockprep' 2 | 3 | export class UnsafeEthereumDataPrep extends EthereumDataPrep { 4 | blockPrepMap: Map 5 | constructor(blockPrepMap: Map, blocknumberOrder: number[], contextBlocknumber: number, expectedStateStr: string, latestBlocknumber: number) { 6 | super(new Map(), blocknumberOrder, contextBlocknumber, expectedStateStr, latestBlocknumber) 7 | this.blockPrepMap = blockPrepMap 8 | } 9 | } 10 | 11 | // name with *Prep to avoid confusion with cle-lib/Block 12 | export class UnsafeBlockPrep extends BlockPrep { 13 | eventOffsets: Uint32Array 14 | constructor(rawblock: Record) { 15 | super(rawblock) 16 | this.eventOffsets = new Uint32Array() 17 | } 18 | 19 | setEventOffsets(eventOffsets: Uint32Array) { 20 | this.eventOffsets = eventOffsets 21 | } 22 | 23 | getEventOffsets() { 24 | return this.eventOffsets 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe/fill.ts: -------------------------------------------------------------------------------- 1 | import { filterEvents } from '../../common/api_helper' 2 | import { toHexString } from '../../common/utils' 3 | import type { BlockPrep } from '../ethereum/blockprep' 4 | 5 | export function unsafeFillInputEvents(input: any, blockPrep: BlockPrep, eventDSAddrList: string[], eventDSEsigsList: string[][]) { 6 | const rawreceiptList = blockPrep?.getRLPReceipts() 7 | 8 | // TODO: return list rather than appending string. 9 | // NODE: rm `matchedEventOffsets` already. please add it yourself. 10 | const [rawReceipts, matchedEventOffsets] = filterEvents( 11 | eventDSAddrList, 12 | eventDSEsigsList, 13 | rawreceiptList as any, 14 | ) 15 | 16 | // TODO: calc receipt count from filterEvents 17 | const receiptCount = (rawReceipts.length > 0 ? rawreceiptList?.length : 0) || 0 18 | input.addInt(receiptCount, false) // receipt count (tmp) 19 | 20 | if (receiptCount > 0) { 21 | // fill raw receipts 22 | input.addVarLenHexString(toHexString(rawReceipts), false) 23 | 24 | input.addVarLenHexString(toHexString(new Uint8Array(matchedEventOffsets.buffer))) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe/index.ts: -------------------------------------------------------------------------------- 1 | import type { Input } from 'zkwasm-toolchain' 2 | import type { CLEYaml } from '../../types' 3 | import { ExtendableEthereumDataSourcePlugin } from '../ethereum' 4 | import { fillInputBlocks, setFillInputEventsFunc } from '../ethereum/fill_blocks' 5 | import { prepareBlocksByYaml, setPrePareOneBlockFunc } from '../ethereum/prepare_blocks' 6 | import { unsafeFillInputEvents } from './fill' 7 | import type { UnsafeEthereumDataPrep } from './dataprep' 8 | import { unsafePrepareOneBlock } from './prepare' 9 | 10 | export class EthereumUnsafeDataSourcePlugin extends ExtendableEthereumDataSourcePlugin { 11 | // SHOULD align with cle-lib/dsp/ 12 | // TODO unsafe 13 | getLibDSPName() { return 'ethereum.unsafe' } 14 | 15 | async prepareData(cleYaml: CLEYaml, prepareParams: Record) { 16 | return unsafePrepareData(cleYaml, prepareParams) 17 | } 18 | 19 | fillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: UnsafeEthereumDataPrep) { 20 | return unsafeFillExecInput(input, cleYaml, dataPrep) 21 | } 22 | } 23 | 24 | export async function unsafePrepareData(cleYaml: CLEYaml, prepareParams: Record) { 25 | const { provider, contextBlocknumber, expectedStateStr } = prepareParams 26 | setPrePareOneBlockFunc(unsafePrepareOneBlock) 27 | 28 | const dataPrep = await prepareBlocksByYaml(provider, contextBlocknumber, expectedStateStr || '', cleYaml) 29 | return dataPrep 30 | } 31 | 32 | export function unsafeFillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: UnsafeEthereumDataPrep) { 33 | // set unsafe func 34 | setFillInputEventsFunc(unsafeFillInputEvents) 35 | return fillInputBlocks(input, cleYaml, dataPrep.blockPrepMap, dataPrep.blocknumberOrder, dataPrep.contextBlocknumber) 36 | } 37 | -------------------------------------------------------------------------------- /src/dsp/ethereum.unsafe/prepare.ts: -------------------------------------------------------------------------------- 1 | import type { providers } from 'ethers' 2 | import { ethers } from 'ethers' 3 | import { RLP } from '@ethereumjs/rlp' 4 | import { safeHex, uint8ArrayToHex } from '../../common/utils' 5 | import { BlockPrep } from '../ethereum/blockprep' 6 | import { dspHooks } from '../hooks' 7 | 8 | export async function unsafePrepareOneBlock(provider: providers.JsonRpcProvider, blockNumber: number, stateDSAddrList: any[], stateDSSlotsList: any[][], needRLPReceiptList: boolean, needTransactions: boolean) { 9 | // let [stateDSAddrList, stateDSSlotsList] = [stateDSAddrList, stateDSSlotsList] 10 | const rawblock = await dspHooks.getBlock(provider, blockNumber) 11 | const block = new BlockPrep(rawblock) 12 | 13 | /** 14 | * prepare storage data 15 | */ 16 | 17 | for (let i = 0; i < stateDSAddrList.length; i++) { 18 | // request 19 | const ethproof = await dspHooks.getProof( 20 | provider, 21 | stateDSAddrList[i], 22 | stateDSSlotsList[i], 23 | ethers.utils.hexValue(blockNumber), 24 | ) 25 | 26 | if (ethproof.balance === '0x0') 27 | ethproof.balance = '' 28 | 29 | if (ethproof.nonce === '0x0') 30 | ethproof.nonce = '' 31 | 32 | const nestedList = [ 33 | Buffer.from(safeHex(ethproof.nonce), 'hex'), 34 | Buffer.from(safeHex(ethproof.balance), 'hex'), 35 | Buffer.from(safeHex(ethproof.storageHash), 'hex'), 36 | Buffer.from(safeHex(ethproof.codeHash), 'hex'), 37 | ] 38 | 39 | const accountRLP = uint8ArrayToHex(RLP.encode(nestedList)) 40 | 41 | block.addFromGetProofResult(ethproof, accountRLP) 42 | } 43 | 44 | /** 45 | * prepare raw receipts data 46 | */ 47 | if (needRLPReceiptList) { 48 | const rawreceiptList = await dspHooks.getRawReceipts(provider, blockNumber).catch( 49 | (error: any) => { 50 | throw error 51 | }, 52 | ) 53 | 54 | block.addRLPReceipts(rawreceiptList) 55 | } 56 | 57 | if (needTransactions) { 58 | const blockwithtxs = await dspHooks.getBlockWithTxs(provider, blockNumber) 59 | block.setTransactions(blockwithtxs.transactions) 60 | } 61 | 62 | return block 63 | } 64 | -------------------------------------------------------------------------------- /src/dsp/ethereum/aux.ts: -------------------------------------------------------------------------------- 1 | import { Input } from 'zkwasm-toolchain' 2 | import type { CLEYaml } from '../../types' 3 | import { u32ListToUint8Array } from '../../common/utils' 4 | import type { BlockPrep, EthereumDataPrep } from './blockprep' 5 | import { MptInput } from './mpt_input' 6 | 7 | export function genAuxParams( 8 | cleYaml: CLEYaml, 9 | dataPrep: EthereumDataPrep, 10 | ) { 11 | let mptInput = new Input() 12 | mptInput = fillMPTInput(mptInput, cleYaml, dataPrep) 13 | 14 | const auxParams = { 15 | mpt: { 16 | private_input: mptInput.getPrivateInputStr(), 17 | public_input: mptInput.getPublicInputStr(), 18 | context_input: mptInput.getContextInputStr(), 19 | }, 20 | adaptor: genAdaptorParams(cleYaml, dataPrep), 21 | extra: genExtra(cleYaml, dataPrep), 22 | } 23 | return auxParams 24 | } 25 | 26 | function fillMPTInput(input: Input, _cleYaml: CLEYaml, dataPrep: EthereumDataPrep) { 27 | const mptIpt = new MptInput(dataPrep.blocknumberOrder.length) 28 | 29 | for (const blockNum of dataPrep.blocknumberOrder) { 30 | // console.log("block number:", blockNum) 31 | const blcokPrepData = dataPrep.blockPrepMap.get(blockNum) 32 | mptIpt.addBlock(blcokPrepData) 33 | // console.log("ctx:", mptIpt.getCtx()) 34 | // console.log("private input:", mptIpt.getPriIpt()) 35 | } 36 | input.append(mptIpt.getCtx(), 2) 37 | input.append(mptIpt.getPriIpt(), 0) 38 | return input 39 | } 40 | 41 | function genAdaptorParams(_cleYaml: CLEYaml, dataPrep: EthereumDataPrep) { 42 | const adaptorParam = { 43 | checkpoint_blocknum: calcCheckpointBlocknum([4321]), // "0x1234" 44 | mpt_blocknums: dataPrep.blocknumberOrder, // ["blocknum1", "blocknum2", ...] 45 | mpt_stateroots: dataPrep.blocknumberOrder.map((bn: any) => { return (dataPrep.blockPrepMap.get(bn) as BlockPrep).stateRoot }), // ["0xstateroot1", "0xstateroot2", ...] 46 | // placeholder 47 | mpt_receiptroots: dataPrep.blocknumberOrder.map((bn: any) => { return (dataPrep.blockPrepMap.get(bn) as BlockPrep).receiptsRoot }), 48 | mpt_txroots: dataPrep.blocknumberOrder.map((bn: any) => { return (dataPrep.blockPrepMap.get(bn) as BlockPrep).transactionsRoot }), 49 | rlp_blockheader: dataPrep.blocknumberOrder.map( 50 | (bn: any) => { return isRecentBlock(bn, dataPrep.latestBlocknumber) ? (dataPrep.blockPrepMap.get(bn) as BlockPrep).rlpheader : null }), // ['0xrecentblockheaderrlp', '' for bho blocknum] 51 | } 52 | return adaptorParam 53 | } 54 | 55 | function isRecentBlock(blocknum: number, latestBlocknumber: number) { 56 | return blocknum > latestBlocknumber - 200 // TODO: fine-tune this. 57 | } 58 | 59 | function calcCheckpointBlocknum(_blockNums: number[]) { 60 | // TODO: enable later 61 | return null 62 | } 63 | 64 | // Used in trigger / verify only 65 | function genExtra(_cleYaml: CLEYaml, dataPrep: EthereumDataPrep): Uint8Array { 66 | // TODO: double check here 67 | const encode = (dict: { [key: string]: any }): Uint8Array => { 68 | const u8a = u32ListToUint8Array(dict.rct_blocknum, 32, true) 69 | return u8a 70 | } 71 | const verifyExtra = { 72 | rct_blocknum: dataPrep.blocknumberOrder.map( 73 | (bn: any) => { return isRecentBlock(bn, dataPrep.latestBlocknumber) ? (dataPrep.blockPrepMap.get(bn) as BlockPrep).number : null }), // ['0xrecentblockheaderrlp', '' for bho blocknum] 74 | } 75 | return encode(verifyExtra) 76 | } 77 | -------------------------------------------------------------------------------- /src/dsp/ethereum/blockprep.ts: -------------------------------------------------------------------------------- 1 | import type { providers } from 'ethers' 2 | import { RLP } from '@ethereumjs/rlp' 3 | import { DataPrep } from '../interface' 4 | import { safeHex, uint8ArrayToHex } from '../../common/utils' 5 | 6 | // includes both exec & prove params 7 | export class EthereumDataPrep extends DataPrep { 8 | blockPrepMap: Map 9 | blocknumberOrder: number[] 10 | contextBlocknumber: number // the blocknum given by user when exec a cle 11 | latestBlocknumber: number // the latest blocknum when proving 12 | constructor(blockPrepMap: Map, blocknumberOrder: number[], contextBlocknumber: number, expectedStateStr: string, latestBlocknumber: number) { 13 | super(expectedStateStr) 14 | this.blockPrepMap = blockPrepMap 15 | this.blocknumberOrder = blocknumberOrder 16 | this.contextBlocknumber = contextBlocknumber 17 | this.latestBlocknumber = latestBlocknumber 18 | } 19 | } 20 | 21 | export class SlotPrep { 22 | key: any 23 | value: any 24 | storageProof: any 25 | constructor( 26 | key: any, 27 | value: any, 28 | storageProof: any, 29 | ) { 30 | this.key = key 31 | this.value = value 32 | this.storageProof = storageProof 33 | } 34 | } 35 | 36 | export class AccountPrep { 37 | address: any 38 | rlpNode: any 39 | accountProof: any 40 | slots: Map 41 | constructor( 42 | address: any, 43 | rlpNode: any, 44 | accountProof: any, 45 | ) { 46 | this.address = address 47 | this.rlpNode = rlpNode 48 | this.accountProof = accountProof 49 | this.slots = new Map() // 50 | } 51 | 52 | addSlot(key: any, value: any, storageProof: any/** string[] */) { 53 | this.slots.set( 54 | key, 55 | new SlotPrep(key, value, storageProof), 56 | ) 57 | } 58 | 59 | getSlot(key: any) { 60 | if (!this.hashSlot(key)) 61 | throw new Error(`Lack data in blockPrep: slot (${key})`) 62 | 63 | return this.slots.get(key) 64 | } 65 | 66 | hashSlot(key: any) { 67 | return this.slots.has(key) 68 | } 69 | 70 | addFromStorageProofList(storageProofList: any[]) { 71 | storageProofList.forEach((sp: { key: any; value: any; proof: any }) => { 72 | this.addSlot(sp.key, sp.value, sp.proof) 73 | }) 74 | } 75 | } 76 | 77 | // name with *Prep to avoid confusion with cle-lib/Block 78 | export class BlockPrep { 79 | rlpheader: string 80 | number: any 81 | timestamp: any 82 | // rlpHeader: any 83 | hash: string 84 | stateRoot: string 85 | receiptsRoot: string 86 | transactionsRoot: string 87 | accounts: Map 88 | rlpreceipts: any[] 89 | transactions: providers.TransactionResponse[] 90 | // constructor(blocknum: number | bigint | BytesLike | Hexable, hash: string, stateRoot: string, receiptsRoot: string, transactionsRoot: string) { 91 | constructor(rawblock: Record) { 92 | this.number = parseInt(rawblock.number, 16) 93 | this.timestamp = parseInt(rawblock.timestamp, 16) 94 | this.hash = rawblock.hash 95 | this.stateRoot = rawblock.stateRoot 96 | this.receiptsRoot = rawblock.receiptsRoot 97 | this.transactionsRoot = rawblock.transactionsRoot 98 | this.rlpheader = this.calcHeaderRLP(rawblock) 99 | this.accounts = new Map() // 100 | this.rlpreceipts = [] 101 | this.transactions = [] 102 | } 103 | 104 | calcHeaderRLP(rawblock: Record): string { 105 | const nestedList = this.formatBlockHeaderForRLP(rawblock) 106 | const blockheaderRLP = uint8ArrayToHex(RLP.encode(nestedList)) 107 | return blockheaderRLP 108 | } 109 | 110 | formatBlockHeaderForRLP(rawblock: Record): Buffer[] { 111 | const nestedList = [ 112 | Buffer.from(safeHex(rawblock.parentHash), 'hex'), 113 | Buffer.from(safeHex(rawblock.sha3Uncles), 'hex'), 114 | Buffer.from(safeHex(rawblock.miner), 'hex'), 115 | Buffer.from(safeHex(rawblock.stateRoot), 'hex'), 116 | Buffer.from(safeHex(rawblock.transactionsRoot), 'hex'), 117 | Buffer.from(safeHex(rawblock.receiptsRoot), 'hex'), 118 | Buffer.from(safeHex(rawblock.logsBloom), 'hex'), 119 | Buffer.from(safeHex(rawblock.difficulty), 'hex'), 120 | Buffer.from(safeHex(rawblock.number), 'hex'), 121 | Buffer.from(safeHex(rawblock.gasLimit), 'hex'), 122 | Buffer.from(safeHex(rawblock.gasUsed), 'hex'), 123 | Buffer.from(safeHex(rawblock.timestamp), 'hex'), 124 | Buffer.from(safeHex(rawblock.extraData), 'hex'), 125 | Buffer.from(safeHex(rawblock.mixHash), 'hex'), 126 | Buffer.from(safeHex(rawblock.nonce), 'hex'), 127 | Buffer.from(safeHex(rawblock.baseFeePerGas), 'hex'), 128 | Buffer.from(safeHex(rawblock.withdrawalsRoot), 'hex'), 129 | ] 130 | return nestedList 131 | } 132 | 133 | addAccount(address: string, rlpAccount: string, accountProof: any) { 134 | this.accounts.set( 135 | address, 136 | new AccountPrep(address, rlpAccount, accountProof), 137 | ) 138 | } 139 | 140 | getAccount(address: string) { 141 | if (!this.hasAccount(address)) 142 | throw new Error(`Lack data in blockPrep: account (${address})`) 143 | 144 | return this.accounts.get(address) 145 | } 146 | 147 | hasAccount(address: string) { 148 | const addressLowercase = address.toLowerCase() 149 | return this.accounts.has(addressLowercase) 150 | } 151 | 152 | addFromGetProofResult(ethproof: { address: any; accountProof: any; storageProof: any }, accountRLP: string | null = null) { 153 | const accountAddress = ethproof.address 154 | 155 | // add Account if not exist. 156 | if (!this.accounts.has(accountAddress)) { 157 | if (accountRLP == null) 158 | throw new Error('lack of accountRLP when new Account') 159 | 160 | this.addAccount(accountAddress, accountRLP, ethproof.accountProof) 161 | } 162 | 163 | this.getAccount(accountAddress)?.addFromStorageProofList(ethproof.storageProof) 164 | } 165 | 166 | addRLPReceipts(rlpReceiptList: any[]) { 167 | rlpReceiptList.forEach((rlpRcpt: any) => { 168 | this.rlpreceipts.push(rlpRcpt) 169 | }) 170 | } 171 | 172 | setTransactions(transactions: providers.TransactionResponse[]) { 173 | this.transactions = transactions 174 | } 175 | 176 | getRLPReceipts() { 177 | return this.rlpreceipts 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/dsp/ethereum/index.ts: -------------------------------------------------------------------------------- 1 | import type { KeyofToArray } from '@murongg/utils/index' 2 | import type { providers } from 'ethers' 3 | import { Input } from 'zkwasm-toolchain' 4 | import { trimPrefix } from '../../common/utils' 5 | import type { CLEYaml } from '../../types' 6 | import type { DataPrep } from '../interface' 7 | import { DataSourcePlugin } from '../interface' 8 | import { dspHooks } from '../hooks' 9 | import type { EthereumDataPrep } from './blockprep' 10 | import { fillInputBlocks, fillInputEvents, setFillInputEventsFunc } from './fill_blocks' 11 | import { prepareBlocksByYaml, prepareOneBlock, setPrePareOneBlockFunc } from './prepare_blocks' 12 | import { genAuxParams } from './aux' 13 | 14 | export { EthereumDataPrep } from './blockprep' 15 | 16 | export interface EthereumDSPPrepareParams { 17 | provider: providers.JsonRpcProvider 18 | contextBlocknumber: number 19 | // contextBlockhash: string 20 | expectedStateStr: string 21 | } 22 | 23 | export interface EthereumDSPExecParams { 24 | provider: providers.JsonRpcProvider 25 | blockId: string 26 | } 27 | 28 | export interface EthereumDSPProveParams { 29 | provider: providers.JsonRpcProvider 30 | blockId: string 31 | expectedStateStr: string 32 | } 33 | 34 | export abstract class ExtendableEthereumDataSourcePlugin extends DataSourcePlugin { 35 | execParams: KeyofToArray = ['provider', 'blockId'] 36 | proveParams: KeyofToArray = ['provider', 'blockId', 'expectedStateStr'] 37 | 38 | async toPrepareParams(params: EthereumDSPExecParams, type: 'exec'): Promise 39 | async toPrepareParams(params: EthereumDSPProveParams, type: 'prove'): Promise 40 | async toPrepareParams(params: EthereumDSPExecParams | EthereumDSPProveParams, type: 'exec' | 'prove') { 41 | let expectedStateStr = '' 42 | const { provider, blockId } = params 43 | if (type === 'prove') 44 | expectedStateStr = (params as EthereumDSPProveParams).expectedStateStr || '' 45 | 46 | // Get block 47 | // TODO: optimize: no need to getblock if blockId is block num 48 | const rawblock = await dspHooks.getBlock(provider, blockId) 49 | const blockNumber = parseInt(rawblock.number) 50 | // const blockHash = rawblock.hash 51 | 52 | return { 53 | provider, 54 | contextBlocknumber: blockNumber, 55 | // contextBlockhash: '-deprecate-', 56 | expectedStateStr, 57 | } 58 | } 59 | 60 | fillProveInput(input: Input, cleYaml: CLEYaml, dataPrep: X) { 61 | this.fillExecInput(input, cleYaml, dataPrep) 62 | // add expected State Str 63 | const expectedStateStr = trimPrefix(dataPrep.expectedStateStr, '0x') 64 | input.addVarLenHexString(expectedStateStr, Input.PublicId) 65 | return input 66 | } 67 | 68 | // TODO: copy instead of rename 69 | toProveDataPrep(execDataPrep: X, execResult: string) { 70 | const proveDataPrep = execDataPrep 71 | proveDataPrep.expectedStateStr = execResult 72 | return proveDataPrep 73 | } 74 | } 75 | 76 | export class EthereumDataSourcePlugin extends ExtendableEthereumDataSourcePlugin { 77 | // SHOULD align with cle-lib/dsp/ 78 | getLibDSPName() { return 'ethereum' } 79 | 80 | async prepareData(cleYaml: CLEYaml, prepareParams: EthereumDSPPrepareParams) { 81 | return safePrepareData(cleYaml, prepareParams) 82 | } 83 | 84 | fillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: EthereumDataPrep) { 85 | // set safe func 86 | setFillInputEventsFunc(fillInputEvents) 87 | input = fillInputBlocks(input, cleYaml, dataPrep.blockPrepMap, dataPrep.blocknumberOrder, dataPrep.contextBlocknumber) 88 | input.auxParams = genAuxParams(cleYaml, dataPrep) 89 | return input 90 | } 91 | 92 | fillProveInput(input: Input, cleYaml: CLEYaml, dataPrep: EthereumDataPrep) { 93 | input = super.fillProveInput(input, cleYaml, dataPrep) 94 | return input 95 | } 96 | } 97 | 98 | export async function safePrepareData(cleYaml: CLEYaml, prepareParams: Record) { 99 | const { provider, contextBlocknumber, expectedStateStr } = prepareParams 100 | setPrePareOneBlockFunc(prepareOneBlock) 101 | 102 | const dataPrep = await prepareBlocksByYaml(provider, contextBlocknumber, expectedStateStr || '', cleYaml) 103 | return dataPrep 104 | } 105 | -------------------------------------------------------------------------------- /src/dsp/ethereum/mpt_input.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import type { BlockPrep } from './blockprep' 3 | 4 | function i32ToLittleEndianHexStr(value: number) { 5 | const buffer = Buffer.alloc(8) 6 | buffer.writeUInt32LE(value, 0) 7 | return buffer.toString('hex') 8 | } 9 | 10 | function prependLengthInLittleEndianHex(hexString: string) { 11 | const lengthInBytes = hexString.length / 2 12 | const lengthAsLittleEndianHex = i32ToLittleEndianHexStr(lengthInBytes) 13 | return lengthAsLittleEndianHex + hexString 14 | } 15 | 16 | function formatProofPath(rawProofPath: string) { 17 | let proofPath = '' 18 | if (rawProofPath.startsWith('0x')) 19 | proofPath = rawProofPath.slice(2) 20 | else 21 | proofPath = rawProofPath 22 | 23 | const newHexString = prependLengthInLittleEndianHex(proofPath) 24 | return newHexString 25 | } 26 | 27 | function safeHex(rawHex: string) { 28 | let hex = '' 29 | if (rawHex.startsWith('0x')) 30 | hex = rawHex.slice(2) 31 | else 32 | hex = rawHex 33 | 34 | if (hex.length % 2 === 0) 35 | return hex 36 | else 37 | return `0${hex}` 38 | } 39 | 40 | function padHexStringToU64LengthLittleEndian(rawHex: string) { 41 | const length = rawHex.length 42 | const remainder = length % 16 43 | if (remainder === 0) 44 | return rawHex 45 | 46 | const paddingSize = 16 - remainder 47 | const paddedHexString = rawHex + '0'.repeat(paddingSize) 48 | return paddedHexString 49 | } 50 | 51 | function hexToLittleEndian(hexString: string) { 52 | const result = hexString.match(/../g)?.reverse().join('') 53 | return result 54 | } 55 | 56 | export class MptInput { 57 | blockCnt: number 58 | priIpt: string 59 | ctx: string 60 | constructor(blockCnt: number) { 61 | this.blockCnt = blockCnt 62 | this.priIpt = '' 63 | // block count 64 | this.ctx = `0x${padHexStringToU64LengthLittleEndian(safeHex(this.blockCnt.toString()))}` 65 | } 66 | 67 | addBlock(blockPrep: BlockPrep) { 68 | this.ctx += this.addBlock2Ctx(blockPrep) 69 | this.priIpt += this.addBlock2PriIpt(blockPrep) 70 | } 71 | 72 | addBlock2Ctx(blockPrep: BlockPrep) { 73 | let currCtx = '' 74 | // block number 75 | currCtx += padHexStringToU64LengthLittleEndian(hexToLittleEndian(safeHex(blockPrep.number.toString(16))) || '') 76 | // account count 77 | const accCnt = blockPrep.accounts.size 78 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(accCnt.toString(16))) 79 | for (const [addr, accData] of blockPrep.accounts) { 80 | // address 81 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(addr)) 82 | // account rlp 83 | currCtx += padHexStringToU64LengthLittleEndian(safeHex((safeHex(accData.rlpNode).length / 2).toString(16))) 84 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(accData.rlpNode)) 85 | // slot count 86 | const slotCnt = accData.slots.size 87 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(slotCnt.toString(16))) 88 | for (const [slotKey, slotData] of accData.slots) { 89 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(slotKey)) 90 | currCtx += padHexStringToU64LengthLittleEndian(safeHex((safeHex(slotData.value).length / 2).toString(16))) 91 | currCtx += padHexStringToU64LengthLittleEndian(safeHex(slotData.value)) 92 | } 93 | } 94 | return currCtx 95 | } 96 | 97 | addBlock2PriIpt(blockPrep: BlockPrep) { 98 | let currPriIpt = '' 99 | // state root 100 | currPriIpt += `0x${safeHex(blockPrep.stateRoot)}:bytes-packed ` 101 | for (const [addr, accData] of blockPrep.accounts) { 102 | // address hash 103 | currPriIpt += `${utils.keccak256(addr)}:bytes-packed ` 104 | // account proof count 105 | currPriIpt += `0x${safeHex(accData.accountProof.length.toString(16))}:i64 ` 106 | let proofStream = ''; let proofHashStream = '' 107 | for (const proof of accData.accountProof) { 108 | proofStream += formatProofPath(safeHex(proof)) 109 | proofHashStream += safeHex(utils.keccak256(proof)) 110 | } 111 | // proof steam length 112 | currPriIpt += `0x${safeHex((proofStream.length / 2).toString(16))}:i64 ` 113 | // proof steam 114 | currPriIpt += `0x${proofStream}:bytes-packed ` 115 | // proof hash steam 116 | currPriIpt += `0x${proofHashStream}:bytes-packed ` 117 | // storage hash 118 | currPriIpt += `0x${blockPrep.transactionsRoot}:bytes-packed ` 119 | for (const [slotKey, slotData] of accData.slots) { 120 | // key hash 121 | currPriIpt += `${utils.keccak256(slotKey)}:bytes-packed ` 122 | // proof count 123 | currPriIpt += `0x${safeHex(slotData.storageProof.length.toString(16))}:i64 ` 124 | let slotPrfStream = ''; let slotPrfHashStream = '' 125 | for (const proof of slotData.storageProof) { 126 | slotPrfStream += formatProofPath(safeHex(proof)) 127 | slotPrfHashStream += safeHex(utils.keccak256(proof)) 128 | } 129 | // slot proof stream length 130 | currPriIpt += `0x${safeHex((slotPrfStream.length / 2).toString(16))}:i64 ` 131 | // slot proof steam 132 | currPriIpt += `0x${slotPrfStream}:bytes-packed ` 133 | // slot proof hash steam 134 | currPriIpt += `0x${slotPrfHashStream}:bytes-packed ` 135 | } 136 | } 137 | return currPriIpt 138 | } 139 | 140 | getCtx() { 141 | return this.ctx 142 | } 143 | 144 | getPriIpt() { 145 | return this.priIpt 146 | } 147 | } 148 | 149 | export default MptInput 150 | -------------------------------------------------------------------------------- /src/dsp/ethereum/prepare_blocks.ts: -------------------------------------------------------------------------------- 1 | import type { providers } from 'ethers' 2 | import { ethers } from 'ethers' 3 | import { RLP } from '@ethereumjs/rlp' 4 | import { safeHex, uint8ArrayToHex } from '../../common/utils' 5 | import type { CLEYaml, EthereumDataSource } from '../../types' 6 | import { dspHooks } from '../hooks' 7 | import { BlockPrep, EthereumDataPrep } from './blockprep' 8 | 9 | export async function prepareBlocksByYaml(provider: providers.JsonRpcProvider, contextBlocknumber: number, expectedStateStr: string, cleYaml: CLEYaml) { 10 | const blockPrepMap = new Map() 11 | 12 | // TODO: multi blocks 13 | const blocknumOrder = [contextBlocknumber] 14 | 15 | await Promise.all(blocknumOrder.map(async (bn) => { 16 | const blockPrep = await prepareOneBlockByYaml(provider, bn, cleYaml) 17 | blockPrepMap.set(bn, blockPrep) 18 | })) 19 | 20 | const latestBlocknumber = await dspHooks.getBlockNumber(provider) // used to decide recent blocks / bho blocks 21 | 22 | return new EthereumDataPrep(blockPrepMap, blocknumOrder, contextBlocknumber, expectedStateStr, latestBlocknumber) 23 | } 24 | 25 | // modularize prepareOneBlockFunc, re-use in other dsp. 26 | let prepareOneBlockFunc = prepareOneBlock 27 | export function setPrePareOneBlockFunc(_func: any) { 28 | prepareOneBlockFunc = _func 29 | } 30 | 31 | export async function prepareOneBlockByYaml(provider: providers.JsonRpcProvider, blockNumber: any, cleYaml: CLEYaml) { 32 | let stateDSAddrList, stateDSSlotsList 33 | const ds = cleYaml.getFilteredSourcesByKind('ethereum')[0] as unknown as EthereumDataSource 34 | if (ds.storage) 35 | [stateDSAddrList, stateDSSlotsList] = ds.getStorageLists() 36 | 37 | else 38 | [stateDSAddrList, stateDSSlotsList] = [[], []] 39 | 40 | const needRLPReceiptList = ds.event != null 41 | const needTransactions = ds.transaction != null 42 | 43 | return await prepareOneBlockFunc(provider, blockNumber, stateDSAddrList, stateDSSlotsList, needRLPReceiptList, needTransactions) 44 | } 45 | 46 | export async function prepareOneBlock(provider: providers.JsonRpcProvider, blockNumber: number, stateDSAddrList: any[], stateDSSlotsList: any[][], needRLPReceiptList: boolean, needTransactions: boolean) { 47 | // let [stateDSAddrList, stateDSSlotsList] = [stateDSAddrList, stateDSSlotsList] 48 | const rawblock = await dspHooks.getBlock(provider, blockNumber) 49 | const block = new BlockPrep(rawblock) 50 | 51 | /** 52 | * prepare storage data 53 | */ 54 | 55 | for (let i = 0; i < stateDSAddrList.length; i++) { 56 | // request 57 | const ethproof = await dspHooks.getProof( 58 | provider, 59 | stateDSAddrList[i], 60 | stateDSSlotsList[i], 61 | ethers.utils.hexValue(blockNumber), 62 | ) 63 | 64 | if (ethproof.balance === '0x0') 65 | ethproof.balance = '' 66 | 67 | if (ethproof.nonce === '0x0') 68 | ethproof.nonce = '' 69 | 70 | const nestedList = [ 71 | Buffer.from(safeHex(ethproof.nonce), 'hex'), 72 | Buffer.from(safeHex(ethproof.balance), 'hex'), 73 | Buffer.from(safeHex(ethproof.storageHash), 'hex'), 74 | Buffer.from(safeHex(ethproof.codeHash), 'hex'), 75 | ] 76 | 77 | const accountRLP = uint8ArrayToHex(RLP.encode(nestedList)) 78 | 79 | block.addFromGetProofResult(ethproof, accountRLP) 80 | } 81 | 82 | /** 83 | * prepare raw receipts data 84 | */ 85 | if (needRLPReceiptList) { 86 | const rawreceiptList = await dspHooks.getRawReceipts(provider, blockNumber) 87 | 88 | block.addRLPReceipts(rawreceiptList) 89 | } 90 | 91 | // TODO: improve this, reduce getBlock times 92 | if (needTransactions) { 93 | const blockwithtxs = await dspHooks.getBlockWithTxs(provider, blockNumber) 94 | block.setTransactions(blockwithtxs.transactions) 95 | } 96 | 97 | return block 98 | } 99 | -------------------------------------------------------------------------------- /src/dsp/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { providers } from 'ethers' 2 | import { isMaybeNumber, toNumber } from '@murongg/utils' 3 | import { getBlock, getBlockWithTxs, getProof, getRawReceipts } from '../common/ethers_helper' 4 | 5 | export type DSPHookKeys = 'getBlock' | 'getProof' | 'getRawReceipts' | 'getBlockWithTxs' | 'getBlockNumber' 6 | export type DSPHooks = Record any> 7 | 8 | const DSP_HOOKS_DATA_CACHE_SIZE_LIMIT = 10 9 | export class DspHooksDataCache { 10 | blockMap: Map 11 | rawReceiptsMap: Map 12 | blockWithTxsMap: Map 13 | constructor() { 14 | this.blockMap = new Map() 15 | this.rawReceiptsMap = new Map() 16 | this.blockWithTxsMap = new Map() 17 | } 18 | 19 | setBlock(provider_url: string, blockid: string, block: any) { 20 | if (block === undefined) 21 | return 22 | if (!this.isSupportedBlockTag(blockid)) 23 | return 24 | this.cleanupExpiredKeys(this.blockMap) 25 | const blockKey = `${provider_url}_${blockid}` 26 | this.blockMap.set(blockKey, block) 27 | } 28 | 29 | hasBlock(provider_url: string, blockid: string): boolean { 30 | const blockKey = `${provider_url}_${blockid}` 31 | return this.blockMap.has(blockKey) 32 | } 33 | 34 | getBlock(provider_url: string, blockid: string): any { 35 | const blockKey = `${provider_url}_${blockid}` 36 | return this.blockMap.get(blockKey) 37 | } 38 | 39 | setRawReceipts(provider_url: string, blockid: string, useDebugRPC: boolean, rawReceipts: any) { 40 | if (rawReceipts === undefined) 41 | return 42 | if (!this.isSupportedBlockTag(blockid)) 43 | return 44 | this.cleanupExpiredKeys(this.rawReceiptsMap) 45 | const rawReceiptsKey = `${provider_url}_${blockid}_${useDebugRPC}` 46 | this.rawReceiptsMap.set(rawReceiptsKey, rawReceipts) 47 | } 48 | 49 | hasRawReceipts(provider_url: string, blockid: string, useDebugRPC: boolean): boolean { 50 | const rawReceiptsKey = `${provider_url}_${blockid}_${useDebugRPC}` 51 | return this.rawReceiptsMap.has(rawReceiptsKey) 52 | } 53 | 54 | getRawReceipts(provider_url: string, blockid: string, useDebugRPC: boolean): any { 55 | const rawReceiptsKey = `${provider_url}_${blockid}_${useDebugRPC}` 56 | return this.rawReceiptsMap.get(rawReceiptsKey) 57 | } 58 | 59 | setBlockWithTxs(provider_url: string, blockid: string, blockWithTxs: any) { 60 | if (blockWithTxs === undefined) 61 | return 62 | if (!this.isSupportedBlockTag(blockid)) 63 | return 64 | this.cleanupExpiredKeys(this.blockWithTxsMap) 65 | const blockWithTxsKey = `${provider_url}_${blockid}` 66 | this.blockWithTxsMap.set(blockWithTxsKey, blockWithTxs) 67 | } 68 | 69 | hasBlockWithTxs(provider_url: string, blockid: string): boolean { 70 | const blockWithTxsKey = `${provider_url}_${blockid}` 71 | return this.blockWithTxsMap.has(blockWithTxsKey) 72 | } 73 | 74 | getBlockWithTxs(provider_url: string, blockid: string): any { 75 | const blockWithTxsKey = `${provider_url}_${blockid}` 76 | return this.blockWithTxsMap.get(blockWithTxsKey) 77 | } 78 | 79 | cleanupExpiredKeys(map: Map): void { 80 | const size = map.size 81 | if (size <= DSP_HOOKS_DATA_CACHE_SIZE_LIMIT) 82 | return 83 | 84 | const keys = map.keys() 85 | for (let i = DSP_HOOKS_DATA_CACHE_SIZE_LIMIT; i < size; i++) { 86 | const key = keys.next().value 87 | map.delete(key) 88 | } 89 | } 90 | 91 | // Do not cache BlockTags that representing dynamic block number. 92 | isSupportedBlockTag(blockid: string): boolean { 93 | if (blockid === 'latest' || blockid === 'pending') 94 | return false 95 | 96 | if (isMaybeNumber(blockid) && toNumber(blockid) < 0) 97 | return false 98 | 99 | return true 100 | } 101 | } 102 | 103 | export const dspDataCache = new DspHooksDataCache() 104 | /** 105 | * modify hooks 106 | * dspHooks.getBlock = () => { 107 | * // do something 108 | * } 109 | */ 110 | export const dspHooks: DSPHooks = { 111 | getBlock: (ethersProvider: providers.JsonRpcProvider, blockid: string): Promise => { 112 | if (dspDataCache.hasBlock(ethersProvider.connection.url, blockid)) 113 | return Promise.resolve(dspDataCache.getBlock(ethersProvider.connection.url, blockid) as any) 114 | 115 | return getBlock(ethersProvider, blockid).then((block: any): any => { 116 | dspDataCache.setBlock(ethersProvider.connection.url, blockid, block) 117 | return block 118 | }) 119 | }, 120 | getProof: (ethersProvider: providers.JsonRpcProvider, address: string, keys: any[], blockid: string) => { 121 | return getProof(ethersProvider, address, keys, blockid) 122 | }, 123 | getRawReceipts: (ethersProvider: providers.JsonRpcProvider, blockid: string | number, useDebugRPC = false): Promise => { 124 | if (dspDataCache.hasRawReceipts(ethersProvider.connection.url, blockid.toString(), useDebugRPC)) 125 | return Promise.resolve(dspDataCache.getRawReceipts(ethersProvider.connection.url, blockid.toString(), useDebugRPC)) 126 | 127 | return getRawReceipts(ethersProvider, blockid, useDebugRPC).then((rawReceipts: any): any => { 128 | dspDataCache.setRawReceipts(ethersProvider.connection.url, blockid.toString(), useDebugRPC, rawReceipts) 129 | return rawReceipts 130 | }) 131 | }, 132 | getBlockWithTxs: (ethersProvider: providers.JsonRpcProvider, blockNumber: number): Promise => { 133 | if (dspDataCache.hasBlockWithTxs(ethersProvider.connection.url, blockNumber.toString())) 134 | return Promise.resolve(dspDataCache.getBlockWithTxs(ethersProvider.connection.url, blockNumber.toString())) 135 | 136 | return getBlockWithTxs(ethersProvider, blockNumber).then((blockWithTxs: any): any => { 137 | dspDataCache.setBlockWithTxs(ethersProvider.connection.url, blockNumber.toString(), blockWithTxs) 138 | return blockWithTxs 139 | }) 140 | }, 141 | getBlockNumber: (ethersProvider: providers.JsonRpcProvider) => { 142 | return ethersProvider.getBlockNumber() 143 | }, 144 | } 145 | 146 | // Pointer, can also modify this by modify dspHooks.getBlock 147 | // export const getBlock = dspHooks.getBlock 148 | -------------------------------------------------------------------------------- /src/dsp/hub.ts: -------------------------------------------------------------------------------- 1 | import type { CLEYaml } from '../types' 2 | import { EthereumDataSourcePlugin } from './ethereum' 3 | import { EthereumOffchainDSP } from './ethereum-offchain.bytes' 4 | import { EthereumUnsafeDataSourcePlugin } from './ethereum.unsafe' 5 | import type { DataSourcePlugin } from './interface' 6 | import { UnsafeSafeETHDSP } from './ethereum.unsafe-ethereum' 7 | 8 | export interface DSPHubForeignKeys { 9 | // isLocal?: boolean 10 | } 11 | 12 | export class DSPHub { 13 | hub: Map> 14 | 15 | constructor() { 16 | this.hub = new Map>() 17 | } 18 | 19 | /** 20 | * @param {string} primaryKey yaml.dataSources[i].kind 21 | * @param {object} foreignKeys {"isLocal": boolean} 22 | * @returns Combined Key String: Better to be human readable 23 | */ 24 | toHubKey(primaryKey: string, _foreignKeys: DSPHubForeignKeys) { 25 | // const { isLocal } = foreignKeys 26 | // const keyFullLocal = (!isLocal || isLocal == null) ? 'full' : 'local' 27 | // return `${primaryKey}:${keyFullLocal}` 28 | return primaryKey 29 | } 30 | 31 | toHubKeyByYaml(cleYaml: CLEYaml, foreignKeys: DSPHubForeignKeys = {}) { 32 | const sigKeys = cleYaml.getSignificantKeys(true) 33 | const primaryKey = this.toPrimaryKey(sigKeys) 34 | return this.toHubKey(primaryKey, foreignKeys) 35 | } 36 | 37 | toPrimaryKey(sigKeys: any[]) { 38 | return sigKeys.map((keys: any[]) => keys.join('.')).join('-') 39 | } 40 | 41 | setDSP(primaryKey: string, foreignKeys: DSPHubForeignKeys, dsp: InstanceType>) { 42 | this.hub.set(this.toHubKey(primaryKey, foreignKeys), dsp) 43 | } 44 | 45 | getDSP(primaryKey: string, foreignKeys: DSPHubForeignKeys = {}): InstanceType | undefined { 46 | const key = this.toHubKey(primaryKey, foreignKeys) 47 | if (!this.hub.has(key)) 48 | throw new Error(`Data Source Plugin Hub Key "${key}" doesn't exist.`) 49 | 50 | return this.hub.get(key) 51 | } 52 | 53 | getDSPByYaml(cleYaml: CLEYaml, foreignKeys: DSPHubForeignKeys = {}) { 54 | const sigKeys = cleYaml.getSignificantKeys(true) 55 | const primaryKey = this.toPrimaryKey(sigKeys) as any 56 | return this.getDSP(primaryKey, foreignKeys) 57 | } 58 | 59 | initialize(): void { 60 | const emptyForeignKey = { } 61 | /** 62 | * Register DSPs 63 | */ 64 | this.setDSP('ethereum', emptyForeignKey, new EthereumDataSourcePlugin()) 65 | this.setDSP('ethereum-offchain.bytes', emptyForeignKey, new EthereumOffchainDSP()) 66 | 67 | // compatible purpose, deprecating 68 | // dspHub.setDSP('ethereum', { isLocal: true }, new EthereumLocalDataSourcePlugin()) 69 | 70 | this.setDSP('ethereum.unsafe', emptyForeignKey, new EthereumUnsafeDataSourcePlugin()) 71 | this.setDSP('ethereum.unsafe-ethereum', emptyForeignKey, new UnsafeSafeETHDSP()) 72 | } 73 | } 74 | 75 | /** 76 | * Global DSP Hub 77 | */ 78 | export const dspHub = new DSPHub() 79 | dspHub.initialize() 80 | -------------------------------------------------------------------------------- /src/dsp/index.ts: -------------------------------------------------------------------------------- 1 | export { DataSourcePlugin, DataPrep } from './interface' 2 | export { dspHub } from './hub' 3 | export { dspHooks } from './hooks' 4 | export type * from './types' 5 | export * as ETHDSP from './ethereum/index' 6 | 7 | // export { EthereumDataSourcePlugin } from "./ethereum/index"; 8 | -------------------------------------------------------------------------------- /src/dsp/interface.ts: -------------------------------------------------------------------------------- 1 | // - prepare data from yaml 2 | // - fill input 3 | // - prep structure 4 | 5 | import type { KeyofToArray } from '@murongg/utils' 6 | import type { Input } from 'zkwasm-toolchain' 7 | import { paramsNormalize } from '../common/utils' 8 | import type { CLEYaml } from '../types' 9 | 10 | export class DataPrep { 11 | expectedStateStr: any 12 | constructor(expectedStateStr: string) { 13 | this.expectedStateStr = expectedStateStr 14 | } 15 | } 16 | 17 | export abstract class DataSourcePlugin { 18 | abstract getLibDSPName(): string 19 | abstract prepareData(cleYaml: CLEYaml, prepareParams: PRP): Promise 20 | abstract fillExecInput(input: Input, cleYaml: CLEYaml, dataPrep: DP): Input 21 | abstract fillProveInput(input: Input, cleYaml: CLEYaml, dataPrep: DP): Input 22 | abstract toProveDataPrep(execDataPrep: DP, execResult: any): DP 23 | 24 | abstract execParams: KeyofToArray 25 | toExecParams(params: Record) { 26 | return paramsNormalize(this.execParams as string[], params) as EP 27 | } 28 | 29 | abstract proveParams: KeyofToArray 30 | toProveParams(params: Record) { 31 | return paramsNormalize(this.proveParams as string[], params) as PP 32 | } 33 | 34 | toPrepareParamsFromExecParams(execParams: EP): Promise { 35 | return this.toPrepareParams(execParams, 'exec') 36 | } 37 | 38 | toPrepareParamsFromProveParams(proveParams: PP): Promise { 39 | return this.toPrepareParams(proveParams, 'prove') 40 | } 41 | 42 | abstract toPrepareParams(params: EP, type: 'exec'): Promise 43 | abstract toPrepareParams(params: PP, type: 'prove'): Promise 44 | abstract toPrepareParams(params: EP | PP, type: 'exec' | 'prove'): Promise 45 | } 46 | -------------------------------------------------------------------------------- /src/dsp/types.ts: -------------------------------------------------------------------------------- 1 | export type * from './ethereum' 2 | export type * from './ethereum.unsafe' 3 | export type * from './ethereum.unsafe-ethereum' 4 | export type * from './ethereum-offchain.bytes' 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export * from './common' 3 | export * from './dsp' 4 | export * from './requests' 5 | export * from './types' 6 | -------------------------------------------------------------------------------- /src/requests/error_handle.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError } from 'axios' 2 | import { logger } from '../common' 3 | 4 | export function handleAxiosError(error: AxiosError): [string, boolean] { 5 | let errMsg = '' 6 | let isRetry = false // stop by default 7 | switch (error.code) { 8 | case 'ENOTFOUND': 9 | // NODE: fix this use `error.response?.config.baseURL` , original code is `error.hostname;` 10 | errMsg = `Can't connect to ${error.response?.config.baseURL}` 11 | break 12 | case 'ERR_BAD_RESPONSE': 13 | if (error.response) { 14 | switch (error.response.status) { 15 | case 500: 16 | errMsg = error.response.data as any 17 | break 18 | case 502: 19 | isRetry = true 20 | break 21 | default: 22 | errMsg = `ERR_BAD_RESPONSE: ${error.response.status} ${error.response.statusText}.` 23 | break 24 | } 25 | break 26 | } 27 | break 28 | case 'ERR_BAD_REQUEST': 29 | errMsg = `ERR_BAD_REQUEST: ${error.response?.status} ${error.response?.statusText}.` 30 | break 31 | default: 32 | logger.log('in handleAxiosError:') 33 | logger.log(error) 34 | errMsg = `HTTP ERROR: ${error.response?.status} ${error.response?.statusText}.` 35 | break 36 | } 37 | return [errMsg, isRetry] 38 | } 39 | -------------------------------------------------------------------------------- /src/requests/index.ts: -------------------------------------------------------------------------------- 1 | export { waitTaskStatus } from './zkwasm_taskdetails' 2 | export { zkwasm_setup } from './zkwasm_setup' 3 | export { zkwasm_prove } from './zkwasm_prove' 4 | export { zkwasm_deploy } from './zkwasm_deploy' 5 | export { zkwasm_imagedetails } from './zkwasm_imagedetails' 6 | 7 | export { ora_prove } from './ora_prove' 8 | export { ora_setup } from './ora_setup' 9 | -------------------------------------------------------------------------------- /src/requests/ora_prove.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosResponse } from 'axios' 2 | import axios from 'axios' 3 | import FormData from 'form-data' 4 | import type { Signer } from 'ethers' 5 | import { InputContextType, ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 6 | import type { Input } from 'zkwasm-toolchain' 7 | import { DEFAULT_URL } from '../common/constants' 8 | import { BatchStyleUnsupport, PaymentError } from '../common/error' 9 | import { logger } from '../common' 10 | import type { ProveOptions } from '../api/prove' 11 | import { BatchStyle } from '../types' 12 | import url from './url' 13 | import { handleAxiosError } from './error_handle' 14 | 15 | /** 16 | * send prove request to ora prover with user_privatekey, should be compatible to zkwasmhub 17 | */ 18 | export async function ora_prove( 19 | image_md5: string, 20 | input: Input, 21 | options: ProveOptions, 22 | ): Promise> { 23 | const { proverUrl = DEFAULT_URL.PROVER, signer, batchStyle = BatchStyle.ZKWASMHUB } = options 24 | 25 | const user_address = (await signer.getAddress()).toLowerCase() 26 | 27 | const privateInputArray = input.getPrivateInputStr().trim().split(' ') 28 | const publicInputArray = input.getPublicInputStr().trim().split(' ') 29 | 30 | const signature = await signMessage(signer, image_md5, publicInputArray, privateInputArray) 31 | const formData = assembleFormData(user_address, image_md5, publicInputArray, privateInputArray) 32 | 33 | // zkwasmhub doesn't accept aux_params 34 | 35 | if (batchStyle === BatchStyle.ORA || batchStyle === BatchStyle.ORA_SINGLE) { 36 | if (proverUrl.startsWith(DEFAULT_URL.ZKWASMHUB)) 37 | throw new BatchStyleUnsupport('zkwasmhub doesn\'t support ORA batch style, use ProverType.ZKWASMHUB instead.') 38 | 39 | formData.append('aux_params', JSON.stringify(input.auxParams)) 40 | } 41 | 42 | const zkwasmHeaders = { 43 | 'X-Eth-Signature': signature, 44 | 'Content-Type': 'multipart/form-data', 45 | } 46 | 47 | const requestConfig = { 48 | method: 'post', 49 | maxBodyLength: Infinity, 50 | url: url.proveWasmImageURL(proverUrl).url, 51 | headers: { 52 | ...zkwasmHeaders, 53 | }, 54 | data: formData, 55 | } 56 | 57 | let errorMessage = '' 58 | 59 | // TODO: should change to setTimeInterval. 60 | const retry_time = 1 61 | let response 62 | let isRetry 63 | for (let i = 0; i < retry_time + 1; i++) { 64 | response = await axios.request(requestConfig).catch((error) => { 65 | [errorMessage, isRetry] = handleAxiosError(error) 66 | if (isRetry) { 67 | // pass 68 | } 69 | else if (errorMessage.startsWith('Payment error')) { 70 | throw new PaymentError(errorMessage) 71 | } 72 | else { 73 | // console.error("Error in ora_prove. Please retry."); 74 | // throw error; 75 | logger.error(error.message) 76 | } 77 | // errorMessage = error.response.data; 78 | }) 79 | if (!isRetry) 80 | break 81 | 82 | // for debug purpose, can delete after stable. 83 | logger.log(errorMessage, 'retrying..') 84 | } 85 | // const response = await axios.request(requestConfig).catch((error) => { 86 | // [errorMessage] = handleAxiosError(error) 87 | // throw error 88 | // }) 89 | 90 | // console.log('response:', response) 91 | return response as AxiosResponse 92 | } 93 | 94 | /** 95 | * assemble formData for zkwasmhub compatibility 96 | */ 97 | function assembleFormData( 98 | user_address: string, 99 | image_md5: string, 100 | public_inputs: string[], 101 | private_inputs: string[], 102 | ) { 103 | const formData = new FormData() 104 | formData.append('user_address', user_address.toLowerCase()) 105 | formData.append('md5', image_md5) 106 | for (let i = 0; i < public_inputs.length; i++) 107 | formData.append('public_inputs', public_inputs[i]) 108 | 109 | for (let i = 0; i < private_inputs.length; i++) 110 | formData.append('private_inputs', private_inputs[i]) 111 | 112 | formData.append('input_context_type', InputContextType.ImageCurrent) 113 | return formData 114 | } 115 | 116 | /** 117 | * sign message for zkwasmhub compatibility 118 | */ 119 | async function signMessage( 120 | signer: Signer, 121 | image_md5: string, 122 | public_inputs: string[], 123 | private_inputs: string[], 124 | ) { 125 | const user_address = (await signer.getAddress()).toLowerCase() 126 | 127 | const message = ZkWasmUtil.createProvingSignMessage({ 128 | user_address: user_address.toLowerCase(), 129 | md5: image_md5.toUpperCase(), 130 | public_inputs, 131 | private_inputs, 132 | input_context_type: InputContextType.ImageCurrent, 133 | }) 134 | 135 | const signature = await signer.signMessage(message) 136 | return signature 137 | } 138 | -------------------------------------------------------------------------------- /src/requests/ora_setup.ts: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data' 2 | import type { AxiosResponse } from 'axios' 3 | import axios from 'axios' 4 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 5 | import { ImageAlreadyExists, PaymentError } from '../common/error' 6 | import type { SetupOptions } from '../api/setup' 7 | import { DEFAULT_CIRCUIT_SIZE, DEFAULT_URL } from '../common/constants' 8 | import { logger } from '../common' 9 | import { handleAxiosError } from './error_handle' 10 | import url from './url' 11 | 12 | export async function ora_setup( 13 | image_md5: string, 14 | image: any, 15 | options: SetupOptions, 16 | ) { 17 | const { 18 | proverUrl = DEFAULT_URL.PROVER, signer, 19 | circuitSize: circuit_size = DEFAULT_CIRCUIT_SIZE, 20 | imageName: name = 'cle.wasm', descriptionUrl: description_url = '', avatorUrl: avator_url = '', 21 | } = options 22 | const user_address = (await signer.getAddress()).toLowerCase() 23 | 24 | // Create Signning Message 25 | const message = ZkWasmUtil.createAddImageSignMessage({ 26 | name, 27 | image_md5: image_md5.toLowerCase(), 28 | image, 29 | user_address, 30 | description_url, 31 | avator_url, 32 | circuit_size, 33 | }) 34 | 35 | const signature = await signer.signMessage(message) 36 | 37 | // Assemble FormData 38 | const formData = new FormData() 39 | formData.append('name', name) 40 | formData.append('image_md5', image_md5.toLowerCase()) 41 | formData.append('image', image) 42 | formData.append('user_address', user_address) 43 | formData.append('description_url', description_url) 44 | formData.append('avator_url', avator_url) 45 | formData.append('circuit_size', circuit_size) 46 | // formData.append("signature", signature); 47 | 48 | const zkwasmHeaders = { 49 | 'X-Eth-Address': user_address, 50 | 'X-Eth-Signature': signature, 51 | } 52 | 53 | const requestConfig = { 54 | method: 'post', 55 | maxBodyLength: Infinity, 56 | url: url.postNewWasmImage(proverUrl).url, 57 | headers: { 58 | ...formData.getHeaders(), 59 | ...zkwasmHeaders, 60 | }, 61 | data: formData, 62 | } 63 | 64 | let response 65 | 66 | let errorMessage = '' 67 | let isRetry 68 | // TODO: should change to setTimeInterval. 69 | const retry_time = 1 70 | for (let i = 0; i < retry_time + 1; i++) { 71 | response = await axios.request(requestConfig).catch((error) => { 72 | [errorMessage, isRetry] = handleAxiosError(error) 73 | if (isRetry) { 74 | // pass 75 | } 76 | else if (errorMessage === 'Error: Image already exists!' 77 | || (errorMessage.startsWith('Image with md5') && errorMessage.endsWith('already exists'))) { 78 | throw new ImageAlreadyExists(errorMessage) 79 | } 80 | else if (errorMessage.startsWith('Payment error')) { 81 | throw new PaymentError(errorMessage) 82 | } 83 | else { 84 | // console.error("Error in zkwasm_setup. Please retry."); 85 | // throw error; 86 | // logger.error('Network Error:', error.message) 87 | throw new Error(`Network Error:${error.message}`) 88 | } 89 | // errorMessage = error.response.data; 90 | }) 91 | if (!isRetry) 92 | break 93 | 94 | // for debug purpose, can delete after stable. 95 | logger.log(errorMessage, 'retrying..') 96 | } 97 | return response as AxiosResponse 98 | } 99 | -------------------------------------------------------------------------------- /src/requests/pinata_upload.ts: -------------------------------------------------------------------------------- 1 | import type FormData from 'form-data' 2 | import type { AxiosResponse } from 'axios' 3 | import axios from 'axios' 4 | import { isFunction } from '@murongg/utils' 5 | import type { UploadResult } from '../api/upload' 6 | import { handleAxiosError } from './error_handle' 7 | import url from './url' 8 | 9 | export interface PinataOptions { 10 | pinataEndpoint: string 11 | pinataJWT: string 12 | directoryName: string 13 | } 14 | 15 | export interface PinitaUploadResult extends UploadResult { 16 | response: AxiosResponse | void 17 | } 18 | 19 | export async function pinata_upload( 20 | formData: FormData, 21 | pinataEndpoint: string, 22 | pinataJWT: string, 23 | ): Promise { 24 | let isUploadSuccess = true 25 | const headers = formData && isFunction(formData.getHeaders) ? formData.getHeaders() : {} 26 | 27 | const requestConfig = { 28 | method: 'post', 29 | maxBodyLength: Infinity, 30 | url: url.uploadToPinata(pinataEndpoint).url, 31 | headers: { 32 | Authorization: `Bearer ${pinataJWT}`, 33 | ...headers, 34 | }, 35 | data: formData, 36 | } 37 | 38 | let errorMessage = '' 39 | const response = await axios.request(requestConfig).catch((error) => { 40 | [errorMessage] = handleAxiosError(error) 41 | isUploadSuccess = false 42 | }) 43 | 44 | return { 45 | response, 46 | success: isUploadSuccess, 47 | isUploadSuccess, // deprecating 48 | errorMessage, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/requests/url.ts: -------------------------------------------------------------------------------- 1 | class EndPoint { 2 | url: string 3 | isProtected: boolean 4 | contentType: Record = {} 5 | constructor(url: string, isProtected: boolean, contentType: Record = {}) { 6 | this.url = url // : string; 7 | this.isProtected = isProtected // : boolean; 8 | this.contentType = contentType // ?: {};} 9 | } 10 | } 11 | 12 | export default { 13 | postNewWasmImage: (zkwasmProverUrl: string) => 14 | new EndPoint(`${zkwasmProverUrl}/setup`, false), 15 | fetchConfiguredMD5: (zkwasmProverUrl: string) => 16 | new EndPoint(`${zkwasmProverUrl}/tasks?tasktype=Setup`, false), 17 | checkWasmImageStatus: (zkwasmProverUrl: string, md5: string, taskType: string | null = null) => 18 | new EndPoint( 19 | `${zkwasmProverUrl}/tasks?md5=${md5}${ 20 | taskType ? `&tasktype=${taskType}` : '' 21 | }`, 22 | false, 23 | ), 24 | deployWasmImageURL: (zkwasmProverUrl: string) => 25 | new EndPoint(`${zkwasmProverUrl}/deploy`, false, { 26 | 'Content-Type': 'application/json', 27 | }), 28 | proveWasmImageURL: (zkwasmProverUrl: string) => 29 | new EndPoint(`${zkwasmProverUrl}/prove`, false, { 30 | 'Content-Type': 'application/json', 31 | }), 32 | getTaskDetails: (zkwasmProverUrl: string, taskId?: string) => 33 | new EndPoint(`${zkwasmProverUrl}/tasks?id=${taskId}`, false, { 34 | 'Content-Type': 'application/json', 35 | }), 36 | searchImageURL: (zkwasmProverUrl: string, md5: string) => 37 | new EndPoint(`${zkwasmProverUrl}/image?md5=${md5}`, false), 38 | getUserBalance: (zkwasmProverUrl: string, address: string) => 39 | new EndPoint( 40 | `${zkwasmProverUrl}/user?user_address=${address}`, 41 | false, 42 | ), 43 | sendTXHash: (zkwasmProverUrl: string, _address: string) => 44 | new EndPoint(`${zkwasmProverUrl}/pay`, false, { 45 | 'Content-Type': 'application/json', 46 | }), 47 | uploadToPinata: (pinataEndpoint: any) => 48 | new EndPoint(`${pinataEndpoint}`, true), 49 | } 50 | -------------------------------------------------------------------------------- /src/requests/zkwasm_deploy.ts: -------------------------------------------------------------------------------- 1 | // Deprecating, no need for deploy. 2 | 3 | import type { AxiosResponse } from 'axios' 4 | import axios from 'axios' 5 | import { Wallet, utils } from 'ethers' 6 | import url from './url' 7 | import { handleAxiosError } from './error_handle' 8 | 9 | // Deploy verification contract 10 | export async function zkwasm_deploy(chain_id: string, user_privatekey: string, image_md5: string, zkwasmProverUrl: string): Promise<[AxiosResponse, boolean, string]> { 11 | let isDeploySuccess = true 12 | 13 | const address = utils.computeAddress(user_privatekey).toLowerCase() 14 | const wallet = new Wallet(user_privatekey) 15 | 16 | const message = JSON.stringify({ 17 | user_address: address, 18 | md5: image_md5, 19 | chain_id, 20 | }) 21 | const signature = await wallet.signMessage(message) 22 | 23 | const requestData = JSON.stringify({ 24 | user_address: address, 25 | md5: image_md5, 26 | chain_id, 27 | signature, 28 | }) 29 | 30 | const requestConfig = { 31 | method: 'post', 32 | maxBodyLength: Infinity, 33 | url: url.deployWasmImageURL(zkwasmProverUrl).url, 34 | data: requestData, 35 | headers: { 36 | 'Content-Type': url.deployWasmImageURL(zkwasmProverUrl).contentType['Content-Type'], 37 | }, 38 | } 39 | 40 | let errorMessage = '' 41 | // NODE: fix this, useless var 42 | // let _ 43 | const response = await axios.request>(requestConfig) 44 | .catch((error) => { 45 | [errorMessage] = handleAxiosError(error) 46 | isDeploySuccess = false 47 | }) 48 | return [response as AxiosResponse, isDeploySuccess, errorMessage] 49 | } 50 | 51 | export async function get_deployed(zkwasmProverUrl: string, image_md5: string) { 52 | const requestConfig = { 53 | method: 'get', 54 | maxBodyLength: Infinity, 55 | url: url.searchImageURL(zkwasmProverUrl, image_md5).url, 56 | } 57 | 58 | let errorMessage = null 59 | const response = await axios.request(requestConfig).catch((error) => { 60 | errorMessage = error 61 | }) 62 | return [response, errorMessage] 63 | } 64 | -------------------------------------------------------------------------------- /src/requests/zkwasm_imagedetails.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { logger } from 'zkwasm-toolchain' 3 | import url from './url.js' 4 | import { handleAxiosError } from './error_handle.js' 5 | 6 | export async function zkwasm_imagedetails(zkwasmProverUrl: string, md5: string) { 7 | const requestConfig = { 8 | method: 'get', 9 | maxBodyLength: Infinity, 10 | url: url.searchImageURL(zkwasmProverUrl, md5.toUpperCase()).url, 11 | headers: { 12 | ...url.searchImageURL(zkwasmProverUrl, md5.toUpperCase()).contentType, 13 | }, 14 | } 15 | 16 | // let errorMessage = null 17 | // const response = await axios.request(requestConfig).catch((error) => { 18 | // errorMessage = error 19 | // }) 20 | 21 | let errorMessage = null 22 | 23 | // TODO: should change to setTimeInterval. 24 | const retry_time = 1 25 | let response 26 | let isRetry 27 | for (let i = 0; i < retry_time + 1; i++) { 28 | response = await axios.request(requestConfig).catch((error) => { 29 | [errorMessage, isRetry] = handleAxiosError(error) 30 | if (isRetry) { 31 | // pass 32 | } 33 | else { 34 | // console.error("Error in ora_prove. Please retry."); 35 | // throw error; 36 | logger.error(error.message) 37 | } 38 | // errorMessage = error.response.data; 39 | }) 40 | if (!isRetry) 41 | break 42 | 43 | // for debug purpose, can delete after stable. 44 | logger.log(errorMessage, 'retrying..') 45 | } 46 | 47 | return [response, errorMessage] 48 | } 49 | -------------------------------------------------------------------------------- /src/requests/zkwasm_imagetask.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import url from './url.js' 3 | 4 | export async function zkwasm_imagetask(zkwasmProverUrl: string, md5: string, taskType: string) { 5 | const requestConfig = { 6 | method: 'get', 7 | maxBodyLength: Infinity, 8 | url: url.checkWasmImageStatus(zkwasmProverUrl, md5.toUpperCase(), taskType).url, 9 | headers: { 10 | ...url.checkWasmImageStatus(zkwasmProverUrl, md5.toUpperCase(), taskType).contentType, 11 | }, 12 | } 13 | 14 | // let errorMessage = null; 15 | const response = await axios.request(requestConfig)// .catch((error) => { 16 | // errorMessage = error; 17 | // }); 18 | // return [response, errorMessage]; 19 | return response 20 | } 21 | -------------------------------------------------------------------------------- /src/requests/zkwasm_prove.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosResponse } from 'axios' 2 | import axios from 'axios' 3 | import FormData from 'form-data' 4 | import { Wallet, utils } from 'ethers' 5 | import { InputContextType, ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 6 | import { handleAxiosError } from './error_handle' 7 | import url from './url' 8 | // import { sign } from "crypto"; 9 | 10 | /** 11 | * send prove request to zkwasmhub 12 | */ 13 | export async function zkwasm_prove( 14 | zkwasmProverUrl: string, 15 | user_privatekey: string, 16 | image_md5: string, 17 | public_inputs: string[], 18 | private_inputs: string[], 19 | ): Promise<[AxiosResponse, boolean, string]> { 20 | let isSetUpSuccess = true 21 | 22 | const user_address = utils.computeAddress(user_privatekey).toLowerCase() 23 | 24 | const message = ZkWasmUtil.createProvingSignMessage({ 25 | user_address: user_address.toLowerCase(), 26 | md5: image_md5.toUpperCase(), 27 | public_inputs, 28 | private_inputs, 29 | input_context_type: InputContextType.ImageCurrent, 30 | }) 31 | 32 | const wallet = new Wallet(user_privatekey) 33 | const signature = await wallet.signMessage(message) 34 | 35 | const formData = new FormData() 36 | formData.append('user_address', user_address.toLowerCase()) 37 | formData.append('md5', image_md5) 38 | for (let i = 0; i < public_inputs.length; i++) 39 | formData.append('public_inputs', public_inputs[i]) 40 | 41 | for (let i = 0; i < private_inputs.length; i++) 42 | formData.append('private_inputs', private_inputs[i]) 43 | 44 | formData.append('input_context_type', InputContextType.ImageCurrent) 45 | 46 | const zkwasmHeaders = { 47 | 'X-Eth-Signature': signature, 48 | 'Content-Type': 'multipart/form-data', 49 | } 50 | 51 | const requestConfig = { 52 | method: 'post', 53 | maxBodyLength: Infinity, 54 | url: url.proveWasmImageURL(zkwasmProverUrl).url, 55 | headers: { 56 | ...zkwasmHeaders, 57 | }, 58 | data: formData, 59 | } 60 | 61 | let errorMessage = '' 62 | // NODE: fix this, useless var 63 | // let _ 64 | const response = await axios.request(requestConfig).catch((error) => { 65 | [errorMessage] = handleAxiosError(error) 66 | isSetUpSuccess = false 67 | }) 68 | 69 | // console.log('response:', response) 70 | return [response as AxiosResponse, isSetUpSuccess as boolean, errorMessage as string] 71 | } 72 | -------------------------------------------------------------------------------- /src/requests/zkwasm_setup.ts: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data' 2 | import type { AxiosResponse } from 'axios' 3 | import axios from 'axios' 4 | import { Wallet, utils } from 'ethers' 5 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 6 | import { ImageAlreadyExists, PaymentError } from '../common/error' 7 | import { logger } from '../common' 8 | import { handleAxiosError } from './error_handle' 9 | import url from './url' 10 | 11 | export async function zkwasm_setup( 12 | ZkwasmProviderUrl: string, 13 | name: string, 14 | image_md5: string, 15 | image: any, 16 | user_privatekey: string, 17 | description_url: string, 18 | avator_url: string, 19 | circuit_size: number, 20 | ) { 21 | const user_address = utils.computeAddress(user_privatekey).toLowerCase() 22 | 23 | const message = ZkWasmUtil.createAddImageSignMessage({ 24 | name, 25 | image_md5: image_md5.toLowerCase(), 26 | image, 27 | user_address, 28 | description_url, 29 | avator_url, 30 | circuit_size, 31 | }) 32 | 33 | const wallet = new Wallet(user_privatekey) 34 | const signature = await wallet.signMessage(message) 35 | 36 | const formData = new FormData() 37 | formData.append('name', name) 38 | formData.append('image_md5', image_md5.toLowerCase()) 39 | formData.append('image', image) 40 | formData.append('user_address', user_address) 41 | formData.append('description_url', description_url) 42 | formData.append('avator_url', avator_url) 43 | formData.append('circuit_size', circuit_size) 44 | // formData.append("signature", signature); 45 | 46 | const zkwasmHeaders = { 47 | 'X-Eth-Address': user_address, 48 | 'X-Eth-Signature': signature, 49 | } 50 | 51 | const requestConfig = { 52 | method: 'post', 53 | maxBodyLength: Infinity, 54 | url: url.postNewWasmImage(ZkwasmProviderUrl).url, 55 | headers: { 56 | ...formData.getHeaders(), 57 | ...zkwasmHeaders, 58 | }, 59 | data: formData, 60 | } 61 | 62 | let response 63 | 64 | let errorMessage = '' 65 | let isRetry 66 | // TODO: should change to setTimeInterval. 67 | const retry_time = 1 68 | for (let i = 0; i < retry_time + 1; i++) { 69 | response = await axios.request(requestConfig).catch((error) => { 70 | [errorMessage, isRetry] = handleAxiosError(error) 71 | if (isRetry) { 72 | // pass 73 | } 74 | else if (errorMessage === 'Error: Image already exists!') { 75 | throw new ImageAlreadyExists(errorMessage) 76 | } 77 | else if (errorMessage.startsWith('Payment error')) { 78 | throw new PaymentError(errorMessage) 79 | } 80 | else { 81 | // console.error("Error in zkwasm_setup. Please retry."); 82 | // throw error; 83 | logger.log(error.message) 84 | } 85 | // errorMessage = error.response.data; 86 | }) 87 | if (!isRetry) 88 | break 89 | 90 | // for debug purpose, can delete after stable. 91 | logger.log(errorMessage, 'retrying..') 92 | } 93 | return response as AxiosResponse 94 | } 95 | -------------------------------------------------------------------------------- /src/requests/zkwasm_taskdetails.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError, AxiosResponse } from 'axios' 2 | import axios from 'axios' 3 | import { logger } from '../common' 4 | import url from './url' 5 | import { handleAxiosError } from './error_handle' 6 | 7 | export async function zkwasm_taskdetails(zkwasmProverUrl: string, taskId: string): Promise<[AxiosResponse, null | AxiosError]> { 8 | // let isSetUpSuccess = true; 9 | 10 | const requestConfig = { 11 | method: 'get', 12 | maxBodyLength: Infinity, 13 | url: url.getTaskDetails(zkwasmProverUrl, taskId).url, 14 | headers: { 15 | ...url.getTaskDetails(zkwasmProverUrl).contentType, 16 | }, 17 | } 18 | 19 | let errorMessage: null | AxiosError = null 20 | const response = await axios.request(requestConfig).catch((error) => { 21 | // isSetUpSuccess = false; 22 | // console.log(error.message) 23 | 24 | // if (error.code == 'ENOTFOUND'){ 25 | // errorMessage = "Can't connect to " + error.hostname; 26 | // }else{ 27 | // console.log(error) 28 | // errorMessage = error.code; 29 | // } 30 | errorMessage = error 31 | // errorMessage = error.response.data;//todo: is this usefull? 32 | }) 33 | return [response as AxiosResponse, errorMessage] 34 | } 35 | 36 | // TODO: timeout 37 | export async function waitTaskStatus( 38 | zkwasmProverUrl: any, 39 | taskId: any, 40 | statuslist: { [x: string]: any }, 41 | interval: number | undefined, 42 | _timeout = 0, 43 | ) { 44 | return new Promise((resolve, reject) => { 45 | const checkStatus = async () => { 46 | const [response, error] = await zkwasm_taskdetails(zkwasmProverUrl, taskId) 47 | 48 | if (error !== null) { 49 | const [errMsg, isRetry] = handleAxiosError(error) 50 | if (isRetry) { 51 | logger.log(errMsg, 'Retry.') 52 | setTimeout(checkStatus, interval) 53 | } 54 | else { 55 | // stop 56 | reject(errMsg) 57 | } 58 | } 59 | else { 60 | const status = response.data.result.data[0].status // Call function A to check data status 61 | let matched = false 62 | for (const i in statuslist) { 63 | if (status === statuslist[i]) { 64 | matched = true 65 | break 66 | } 67 | } 68 | if (matched) 69 | resolve(response.data.result.data[0]) // Resolve the promise when the status is matched 70 | 71 | else 72 | setTimeout(checkStatus, interval) // Call checkStatus function again after a 1-second delay 73 | } 74 | } 75 | 76 | checkStatus() // Start checking the data status 77 | }) 78 | } 79 | 80 | function millToHumanReadable(mill: number) { 81 | const min = Math.floor(mill / 60000) 82 | const sec = (mill % 60000) / 1000 83 | return `${min} min ${sec} sec` 84 | } 85 | 86 | export function taskPrettyPrint(resData: { submit_time: string | number | Date; process_started: number; process_finished: number }, prefix = '') { 87 | logger.log(`${prefix}Task submit time: ${resData.submit_time}`) 88 | logger.log(`${prefix}Process started: ${resData.process_started}`) 89 | logger.log(`${prefix}Process finished: ${resData.process_finished}`) 90 | logger.log( 91 | `${prefix}Pending time: ${millToHumanReadable( 92 | // @ts-expect-error TODO: fix this, it's incorrect, should new Date().getTime() or other 93 | new Date(resData.process_started) - new Date(resData.submit_time), 94 | )}`, 95 | ) 96 | logger.log( 97 | `${prefix}Running time: ${millToHumanReadable( 98 | // @ts-expect-error TODO: fix this, it's incorrect, should new Date().getTime() or other 99 | new Date(resData.process_finished) - new Date(resData.process_started), 100 | )}`, 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | // TODO rename to api/interface.ts 2 | // Only define common interface here to reduce possible conflicts 3 | import type { Signer } from 'ethers' 4 | import type { CLEYaml } from '../yaml' 5 | 6 | export interface CLEExecutable { 7 | wasmUint8Array: Uint8Array 8 | cleYaml: CLEYaml 9 | } 10 | 11 | export interface ProofParams { 12 | aggregate_proof: Uint8Array 13 | batch_instances: Uint8Array 14 | aux: Uint8Array 15 | instances: Uint8Array[] 16 | extra?: Uint8Array 17 | } 18 | 19 | export enum BatchStyle { 20 | ORA, 21 | ORA_SINGLE, 22 | ZKWASMHUB, 23 | } 24 | export interface BatchOption { 25 | batchStyle?: BatchStyle 26 | } 27 | 28 | export interface SingableProver { 29 | proverUrl: string 30 | signer: Signer 31 | } 32 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../yaml' 2 | export * from './api' 3 | -------------------------------------------------------------------------------- /src/yaml/cleyaml_eth.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { YamlHealthCheckFailed, YamlNotSupported } from '../common/error' 3 | import { logger } from '../common' 4 | import type { DataDestinationKind, DataSourceKind } from './interface' 5 | import { DataDestination, DataSource } from './interface' 6 | 7 | function isEthereumAddress(address: string) { 8 | try { 9 | const parsedAddress = ethers.utils.getAddress(address) 10 | return parsedAddress !== '0x0000000000000000000000000000000000000000' 11 | } 12 | catch (error) { 13 | return false 14 | } 15 | } 16 | class EventItem { 17 | constructor( 18 | public address: string, 19 | public events: string[], 20 | ) { 21 | // this.address = address.toLowerCase() 22 | } 23 | } 24 | class StorageItem { 25 | constructor( 26 | public address: string, 27 | public slots: string[], // ethers.utils.BytesLike[] 28 | ) { 29 | // this.address = address.toLowerCase() 30 | } 31 | } 32 | class TransactionItem { 33 | constructor( 34 | public from: string, 35 | public to: string, 36 | ) { 37 | this.from = from.toLowerCase() 38 | this.to = to.toLowerCase() 39 | } 40 | } 41 | class EventSectionCache { 42 | constructor( 43 | public addressList: string[], 44 | public esigsList: string[][], 45 | ) { 46 | this.addressList = addressList.map(item => item.toLowerCase()) 47 | } 48 | } 49 | class StorageSectionCache { 50 | constructor( 51 | public addressList: string[], 52 | public slotsList: string[][], 53 | ) { 54 | this.addressList = addressList.map(item => item.toLowerCase()) 55 | } 56 | } 57 | 58 | export class EthereumDataSource extends DataSource { 59 | unsafe: boolean 60 | network: string 61 | // event: EventSection | null 62 | // storage: StorageSection | null 63 | event: EventItem[] | null 64 | storage: StorageItem[] | null 65 | transaction: TransactionItem[] | null 66 | account: any[] | null 67 | block: any 68 | 69 | eventSectionCache: EventSectionCache | null = null 70 | storageSectionCache: StorageSectionCache | null = null 71 | 72 | constructor( 73 | yamlObj: any, 74 | kind: DataSourceKind, 75 | unsafe: boolean, 76 | network: string, 77 | event: EventItem[] | null, 78 | storage: StorageItem[] | null, 79 | transaction: TransactionItem[] | null, 80 | account: any[] | null, 81 | block: any, 82 | ) { 83 | super(yamlObj, kind) 84 | this.unsafe = unsafe 85 | this.network = network 86 | this.event = event 87 | this.storage = storage 88 | this.transaction = transaction 89 | this.account = account 90 | this.block = block 91 | } 92 | 93 | static from_v_0_0_2(yamlEthDS: { kind: DataSourceKind; unsafe?: boolean; network: string; event: EventItem[]; storage: StorageItem[]; transaction: TransactionItem[] }) { 94 | return new EthereumDataSource( 95 | yamlEthDS, 96 | yamlEthDS.kind, 97 | yamlEthDS.unsafe == null ? false : yamlEthDS.unsafe, 98 | yamlEthDS.network, 99 | // (yamlEthDS.event != null) ? EventSection.from_v_0_0_2(yamlEthDS.event) : null, 100 | // (yamlEthDS.storage != null) ? StorageSection.from_v_0_0_2(yamlEthDS.storage) : null, 101 | yamlEthDS.event, 102 | yamlEthDS.storage, 103 | yamlEthDS.transaction, 104 | null, // TODO: account section 105 | null, // TODO: block section 106 | ) 107 | } 108 | 109 | static from_v_0_0_1(_yamlEthDS: any) { 110 | throw new Error('no 0.0.1 support') // TODO 111 | } 112 | 113 | // signaficant to decide which lib dsp main it should use. 114 | getSignificantKeys(): string[] { 115 | return this.unsafe ? [this.kind, 'unsafe'] : [this.kind] 116 | } 117 | 118 | getEventLists(useCache = true) { 119 | // return if there's cache, cause it's always the same 120 | if (!useCache || this.eventSectionCache == null) { 121 | const loadFromEventSource = (eventItem: EventItem) => { 122 | const source_address = eventItem.address 123 | const source_esigs = eventItem.events.map((ed: string) => { 124 | const eventHash = ed.startsWith('0x') ? ed : ethers.utils.keccak256(ethers.utils.toUtf8Bytes(ed)) 125 | return eventHash 126 | }) 127 | 128 | return [source_address, source_esigs] as [string, string[]] 129 | } 130 | 131 | const eventDSAddrList: string[] = [] 132 | const eventDSEsigsList: string[][] = [] 133 | if (this.event) 134 | this.event.forEach((event: any) => { const [sa, se] = loadFromEventSource(event); eventDSAddrList.push(sa); eventDSEsigsList.push(se) }) 135 | 136 | this.eventSectionCache = new EventSectionCache(eventDSAddrList, eventDSEsigsList) 137 | } 138 | return [this.eventSectionCache.addressList, this.eventSectionCache.esigsList] as [string[], string[][]] 139 | } 140 | 141 | getStorageLists(useCache = true) { 142 | // return if there's cache, cause it's always the same 143 | if (!useCache || this.storageSectionCache == null) { 144 | const loadFromStorageSource = (storage: StorageItem) => { 145 | const source_address = storage.address.toLowerCase() 146 | const source_slots = storage.slots.map((sl: ethers.utils.BytesLike) => { 147 | return ethers.utils.hexZeroPad(sl, 32) 148 | }) 149 | 150 | return [source_address, source_slots] as [string, string[]] 151 | } 152 | 153 | const stateDSAddrList: string[] = [] 154 | const stateDSSlotsList: string[][] = [] 155 | if (this.storage) { 156 | this.storage.forEach((storage: any) => { 157 | const [sa, sl] = loadFromStorageSource(storage) 158 | stateDSAddrList.push(sa) 159 | stateDSSlotsList.push(sl) 160 | }) 161 | } 162 | this.storageSectionCache = new StorageSectionCache(stateDSAddrList, stateDSSlotsList) 163 | } 164 | return [this.storageSectionCache.addressList, this.storageSectionCache.slotsList] as [string[], string[][]] 165 | } 166 | 167 | static healthCheck(ds: { kind: DataSourceKind; unsafe?: boolean; network: string; event: any; storage: any; transaction: any }) { 168 | const validUnsafeType = ['boolean', 'undefined'] 169 | if (!validUnsafeType.includes(typeof ds.unsafe)) 170 | throw new YamlHealthCheckFailed(`unsafe only accept boolean when defined, provided: ${typeof ds.unsafe}`) 171 | 172 | if (ds.network == null) 173 | throw new YamlHealthCheckFailed('missing \'network\' in Ethereum DS') 174 | 175 | // for safe mode, only accept 1 kind of eth state due to proving limits 176 | if (ds.unsafe !== true) { 177 | const eventCount = ds.event ? 1 : 0 178 | const storageCount = ds.storage ? 1 : 0 179 | const transactionCount = ds.transaction ? 1 : 0 180 | 181 | if (eventCount + storageCount + transactionCount !== 1) 182 | throw new YamlNotSupported('currently safe mode supports only either \'event\' or \'storage\' or \'transaction\' field, try "unsafe: true" for partially proof cases.') 183 | } 184 | 185 | if (ds.transaction) 186 | logger.warn('Ethereum transaction section is still EXPERIMENTAL, use at your own risks.') 187 | } 188 | } 189 | 190 | export class EthereumDataDestination extends DataDestination { 191 | network: string 192 | address: string 193 | constructor(yamlObj: any, kind: DataDestinationKind, network: string, address: string) { 194 | super(yamlObj, kind) 195 | this.network = network 196 | this.address = address 197 | } 198 | 199 | // signaficant to decide which lib dsp main it should use. 200 | getSignificantKeys() { 201 | return [this.kind] 202 | } 203 | 204 | static from_v_0_0_2(yamlEthDD: { kind: DataDestinationKind; network: string; address: string }) { 205 | return new EthereumDataDestination( 206 | yamlEthDD, 207 | yamlEthDD.kind, 208 | yamlEthDD.network, 209 | yamlEthDD.address, 210 | ) 211 | } 212 | 213 | static from_v_0_0_1(yamlEthDD: { kind: DataDestinationKind; network: string; destination: { address: string } }) { 214 | return new EthereumDataDestination( 215 | yamlEthDD, 216 | yamlEthDD.kind, 217 | yamlEthDD.network, 218 | yamlEthDD.destination.address, 219 | ) 220 | } 221 | 222 | static healthCheck(dd: { network: string; address: string }) { 223 | // data destination must have network and address 224 | if (!dd.network || !dd.address) 225 | throw new YamlHealthCheckFailed('dataDestinations object is missing required fields') 226 | 227 | // address must be the ethereum address and not address zero 228 | if (!isEthereumAddress(dd.address)) 229 | throw new YamlHealthCheckFailed('Invalid Ethereum address in dataDestinations') 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/yaml/cleyaml_off.ts: -------------------------------------------------------------------------------- 1 | import { YamlHealthCheckFailed, YamlNotSupported } from '../common/error' 2 | import { DataSource } from './interface' 3 | 4 | // can't do this, because of lib handleFunc requires static type 5 | // class CustomDataSection { 6 | // constructor(_witness, _public) { 7 | // this.witness = _witness; 8 | // this.public = _public; 9 | // } 10 | // static from_v_0_0_2(cds){ 11 | // return new CustomDataSection(cds.witness, cds.public); 12 | // } 13 | // static healthCheck(cds){ 14 | // if (!cds.witness) { 15 | // throw new YamlHealthCheckFailed('`offchain.custom.data` is missing `witness`'); 16 | // } 17 | // if (cds.public) { 18 | // throw new YamlNotSupported('`offchain.custom.data.public` is not supported yet'); 19 | // } 20 | // } 21 | // } 22 | 23 | export class OffchainDataSource extends DataSource { 24 | type: any 25 | constructor(yamlObj: any, kind: any, type: any) { 26 | super(yamlObj, kind) 27 | this.type = type 28 | } 29 | 30 | // signaficant to decide which lib dsp main it should use. 31 | getSignificantKeys(): string[] { 32 | return [this.kind, this.type] 33 | } 34 | 35 | static from_v_0_0_2(yamlOffDS: { kind: any; type: any }) { 36 | return new OffchainDataSource( 37 | yamlOffDS, 38 | yamlOffDS.kind, 39 | yamlOffDS.type, 40 | // CustomDataSection.from_v_0_0_2(yamlOffDS.data) 41 | ) 42 | } 43 | 44 | static from_v_0_0_1(_yamlOffDS: any) { 45 | throw new Error('offchain dataSource is only supported in spec >= v0.0.2') 46 | } 47 | 48 | static healthCheck(yamlOffDS: { kind: string; type: string }) { 49 | if (yamlOffDS.kind !== 'offchain') 50 | throw new YamlHealthCheckFailed(`offchain dataSource is parsing wrong 'kind'. expect offchain, but got ${yamlOffDS.kind}.`) 51 | 52 | if (!yamlOffDS.type) 53 | throw new YamlHealthCheckFailed('offchain dataSource missing `type`') 54 | 55 | const validType = ['bytes'] 56 | // const validType = ['bytes', 'arraybytes', 'multiarraybytes'] 57 | 58 | if (!validType.includes(yamlOffDS.type)) 59 | throw new YamlNotSupported(`Invalid offchain dataSource \`type\`, only support ${validType.toString()}`) 60 | 61 | // if (ds.type == 'bytes') { 62 | // if (!ds.data) { 63 | // throw new YamlHealthCheckFailed('offchain dataSource custom type missing `data`'); 64 | // } 65 | // CustomDataSection.healthCheck(ds.data) 66 | // } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/yaml/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cleyaml' 2 | export * from './interface' 3 | export * from './cleyaml_eth' 4 | export * from './cleyaml_off' 5 | -------------------------------------------------------------------------------- /src/yaml/interface.ts: -------------------------------------------------------------------------------- 1 | import yaml from 'js-yaml' 2 | 3 | export type DataSourceKind = 'ethereum' | 'offchain' 4 | export type DataDestinationKind = 'ethereum' | 'offchain' 5 | 6 | export class WrappedYaml { 7 | yamlObj: any 8 | constructor(yamlObj: any) { 9 | this.yamlObj = yamlObj 10 | } 11 | 12 | toString() { 13 | return yaml.dump(this.yamlObj) 14 | } 15 | 16 | filterByKeys(keysToInclude: string[]) { 17 | const filteredObject: any = {} 18 | Object.keys(this.yamlObj).forEach((key) => { 19 | if (keysToInclude.includes(key)) 20 | filteredObject[key] = this.yamlObj[key] 21 | }) 22 | return filteredObject 23 | } 24 | } 25 | 26 | export class DataSource extends WrappedYaml { 27 | kind: DataSourceKind 28 | constructor(yamlObj: any, kind: DataSourceKind) { 29 | super(yamlObj) 30 | this.kind = kind 31 | } 32 | 33 | getSignificantKeys(): string[] { 34 | throw new Error(`default: getSignificantKeys not implemented for DataSource kind ${this.kind}.`) 35 | } 36 | 37 | healthCheck() { 38 | throw new Error(`default: healthCheck not implemented for DataSource kind ${this.kind}.`) 39 | } 40 | } 41 | 42 | export class DataDestination extends WrappedYaml { 43 | kind: DataDestinationKind 44 | constructor(yamlObj: any, kind: DataDestinationKind) { 45 | super(yamlObj) 46 | this.kind = kind 47 | } 48 | 49 | getSignificantKeys(): string[] { 50 | throw new Error(`default: getSignificantKeys not implemented for DataDestination kind ${this.kind}.`) 51 | } 52 | 53 | healthCheck() { 54 | throw new Error(`default: healthCheck not implemented for DataDestination kind ${this.kind}.`) 55 | } 56 | } 57 | 58 | export class Mapping { 59 | language: string 60 | file: string 61 | handler: string 62 | constructor(language: string, file: string, handler: string) { 63 | this.language = language 64 | this.file = file 65 | this.handler = handler 66 | } 67 | 68 | static from_v_0_0_2(yamlMapping: { language: string; file: string; handler: string }) { 69 | return new Mapping( 70 | yamlMapping.language, 71 | yamlMapping.file, 72 | yamlMapping.handler, 73 | ) 74 | } 75 | 76 | static from_v_0_0_1(_yamlMapping: { language: string; file: string; handler: string }) { 77 | return null // not important for 0.0.1 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/compile.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { fixtures } from './fixureoptions' 3 | import { testCompile } from './compile_test_impl' 4 | 5 | (global as any).__BROWSER__ = false 6 | 7 | const pathfromfixtures = 'dsp/ethereum(storage)' 8 | // const pathfromfixtures = 'dsp/ethereum(event)' 9 | // const pathfromfixtures = 'dsp/ethereum.unsafe' 10 | const option = fixtures[pathfromfixtures] 11 | 12 | describe('test compile', async () => { 13 | it('test compile', async () => { 14 | await testCompile(option) 15 | }, { timeout: 100000 }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/compile_test_impl.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { expect } from 'vitest' 3 | import { objectKeys } from '@murongg/utils' 4 | import webjson from '@ora-io/cle-lib/test/weblib/weblib.json' 5 | import { DEFAULT_PATH } from '../src/common/constants' 6 | import * as cleapi from '../src/index' 7 | import { loadYamlFromPath } from './utils/yaml' 8 | import { createOnNonexist } from './utils/file' 9 | import { config } from './config' 10 | 11 | (global as any).__BROWSER__ = false 12 | 13 | function readFile(filepath: string) { 14 | return fs.readFileSync(filepath, 'utf-8') 15 | } 16 | 17 | export async function testCompile(option: any) { 18 | const { mappingPath, yamlPath, wasmPath, watPath } = option 19 | const cleYaml = loadYamlFromPath(yamlPath) 20 | if (!cleYaml) 21 | throw new Error('yaml is null') 22 | 23 | const sources = { 24 | ...webjson, 25 | 'src/mapping.ts': readFile(mappingPath), 26 | 'src/cle.yaml': readFile(yamlPath), 27 | } 28 | 29 | const result = await cleapi.compile(sources, { compilerServerEndpoint: config.CompilerServerEndpoint }) 30 | 31 | if ((result?.stderr as any)?.length > 0) 32 | throw new Error(result?.stderr?.toString()) 33 | 34 | expect(result.error).toBeNull() 35 | expect(objectKeys(result.outputs).length).toBeGreaterThanOrEqual(1) 36 | const wasmContent = result.outputs[DEFAULT_PATH.OUT_WASM] 37 | const watContent = result.outputs[DEFAULT_PATH.OUT_WAT] 38 | expect(wasmContent).toBeDefined() 39 | expect(watContent).toBeDefined() 40 | 41 | // optional: output compile result for further exec test 42 | createOnNonexist(wasmPath) 43 | fs.writeFileSync(wasmPath, wasmContent) 44 | fs.writeFileSync(watPath, watContent) 45 | } 46 | -------------------------------------------------------------------------------- /tests/config-example.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_URL } from '../src/common/constants' 2 | 3 | export const config = { 4 | JsonRpcProviderUrl: { // Erigon node rpc are highly recommended here. 5 | mainnet: 'https://rpc.ankr.com/eth', 6 | sepolia: 'https://rpc.ankr.com/eth_sepolia', 7 | }, 8 | UserPrivateKey: '0x{key}', 9 | CompilerServerEndpoint: DEFAULT_URL.COMPILER_SERVER, 10 | ZkwasmProviderUrl: DEFAULT_URL.ZKWASMHUB, 11 | } 12 | -------------------------------------------------------------------------------- /tests/deposit.test.ts: -------------------------------------------------------------------------------- 1 | import { it } from 'vitest' 2 | import { ethers } from 'ethers' 3 | import * as cleapi from '../src/index' 4 | import { config } from './config' 5 | 6 | (global as any).__BROWSER__ = false 7 | 8 | it('test deposit', async () => { 9 | const rpcUrl = config.JsonRpcProviderUrl.sepolia 10 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 11 | const deployedContractAddress = '0x870ef9B5DcBB6F71139a5f35D10b78b145853e69' 12 | const depositAmount = '0.001' 13 | const userPrivateKey = config.UserPrivateKey 14 | const signer = new ethers.Wallet(userPrivateKey, provider) 15 | const result = await cleapi.deposit(deployedContractAddress, signer, { depositAmount }) 16 | 17 | console.log(result) 18 | }, { timeout: 100000 }) 19 | -------------------------------------------------------------------------------- /tests/dispatch.test.ts: -------------------------------------------------------------------------------- 1 | import { it } from 'vitest' 2 | import { ethers } from 'ethers' 3 | import * as cleapi from '../src/index' 4 | import { config } from './config' 5 | 6 | (global as any).__BROWSER__ = false 7 | 8 | it('test dispatch', async () => { 9 | const queryAPI = 'https://zkwasm.hyperoracle.io/td' 10 | const contractAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3' 11 | const feeInWei = ethers.utils.parseEther('0.01') 12 | const rpcUrl = config.JsonRpcProviderUrl.sepolia 13 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 14 | const privateKey = config.UserPrivateKey 15 | const signer = new ethers.Wallet(privateKey, provider) 16 | 17 | const dispatcher = new cleapi.TaskDispatch(queryAPI, contractAddress, feeInWei, provider, signer) 18 | const tx = await dispatcher.setup('cle', 22) 19 | await tx.wait() 20 | 21 | const txhash = tx.hash 22 | console.log(txhash) 23 | 24 | const taskID = await dispatcher.queryTask(txhash) 25 | console.log(taskID) 26 | }, { timeout: 100000 }) 27 | -------------------------------------------------------------------------------- /tests/dsp-hub.test.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it, vi } from 'vitest' 2 | import type { CLEYaml } from '../src' 3 | import { EthereumDataSourcePlugin } from '../src/dsp/ethereum' 4 | import type { DSPHubForeignKeys } from '../src/dsp/hub' 5 | import { DSPHub, dspHub } from '../src/dsp/hub' 6 | 7 | const dspHub4test = new DSPHub() 8 | 9 | describe('DSPHub', () => { 10 | it.only('print all dsp key', () => { 11 | console.log('dsp keys', dspHub.hub.keys()) 12 | }) 13 | beforeEach(() => { 14 | // Clear the hub before each test 15 | dspHub4test.hub.clear() 16 | }) 17 | 18 | it('should set and get DSPs correctly', () => { 19 | const primaryKey = 'ethereum' 20 | const foreignKeys: DSPHubForeignKeys = { } 21 | // const foreignKeys: DSPHubForeignKeys = { isLocal: false } 22 | const dsp = new EthereumDataSourcePlugin() 23 | 24 | dspHub4test.setDSP(primaryKey, foreignKeys, dsp) 25 | 26 | expect(dspHub4test.getDSP(primaryKey, foreignKeys)).toBe(dsp) 27 | }) 28 | 29 | it('should throw an error when getting a non-existent DSP', () => { 30 | const primaryKey = 'ethereum' 31 | const foreignKeys: DSPHubForeignKeys = { } 32 | // const foreignKeys: DSPHubForeignKeys = { isLocal: false } 33 | 34 | expect(() => { 35 | dspHub4test.getDSP(primaryKey, foreignKeys) 36 | }).toThrowError(`Data Source Plugin Hub Key "${primaryKey}" doesn't exist.`) 37 | }) 38 | 39 | it('should get DSP by YAML correctly', () => { 40 | const cleYaml = { 41 | getSignificantKeys: vi.fn(() => [['ethereum']]), 42 | } 43 | const foreignKeys: DSPHubForeignKeys = { } 44 | // const foreignKeys: DSPHubForeignKeys = { isLocal: false } 45 | const dsp = new EthereumDataSourcePlugin() 46 | 47 | dspHub4test.setDSP('ethereum', foreignKeys, dsp) 48 | 49 | expect(dspHub4test.getDSPByYaml(cleYaml as unknown as CLEYaml, foreignKeys)).toBe(dsp) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/dsp.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'vitest' 2 | import { fixtures } from './fixureoptions' 3 | import { testCompile } from './compile_test_impl' 4 | import { testExecute } from './exec_test_impl' 5 | 6 | (global as any).__BROWSER__ = false 7 | 8 | const pathfromfixtures = 'dsp/ethereum(storage)' 9 | // const pathfromfixtures = 'dsp/ethereum.unsafe-ethereum' 10 | const option = fixtures[pathfromfixtures] 11 | 12 | describe(`test dsp: ${pathfromfixtures}`, () => { 13 | it('test compile', async () => { 14 | await testCompile(option) 15 | }, { timeout: 100000 }) 16 | 17 | it('test exec', async () => { 18 | await testExecute(option) 19 | }, { timeout: 100000 }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/exec.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { providers } from 'ethers' 3 | import { describe, expect, it } from 'vitest' 4 | import { loadConfigByNetwork, toHexString } from '../src/common/utils' 5 | import * as cleapi from '../src/index' 6 | import { DSPNotFound } from '../src/common/error' 7 | import { config } from './config' 8 | import { loadYamlFromPath } from './utils/yaml' 9 | import { fixtures } from './fixureoptions' 10 | import { testExecute } from './exec_test_impl' 11 | 12 | (global as any).__BROWSER__ = false 13 | 14 | const pathfromfixtures = 'dsp/ethereum(storage)' 15 | // const pathfromfixtures = 'dsp/ethereum.unsafe-ethereum' 16 | const option = fixtures[pathfromfixtures] 17 | 18 | // enable this to silence logs 19 | // cleapi.setCLELogger(new cleapi.SilentLogger()) 20 | 21 | describe('test exec', () => { 22 | it('test exec', async () => { 23 | await testExecute(option) 24 | }, { timeout: 100000 }) 25 | 26 | it('test_exec_with_prepare_data', async () => { 27 | const { wasmPath, yamlPath, expectedState, blocknum } = option 28 | 29 | const wasm = fs.readFileSync(wasmPath) 30 | const wasmUint8Array = new Uint8Array(wasm) 31 | // const yamlContent = fs.readFileSync(yamlPath, 'utf-8') 32 | const yaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 33 | const dsp = cleapi.dspHub.getDSPByYaml(yaml) 34 | if (!dsp) 35 | throw new DSPNotFound('DSP not found') 36 | 37 | const jsonRpcUrl = loadConfigByNetwork(yaml, config.JsonRpcProviderUrl, true) 38 | const provider = new providers.JsonRpcProvider(jsonRpcUrl) 39 | const generalParams = { 40 | provider, 41 | blockId: loadConfigByNetwork(yaml, blocknum, true), // for storage 42 | // blockId: loadConfigByNetwork(yaml, blocknumForEventTest, true), // for event 43 | } 44 | 45 | const execParams = dsp?.toExecParams(generalParams) 46 | 47 | /** 48 | * Prepare Data, can construct your own dataPrep based on this. 49 | * the actual dataPrep here is instance of cleapi.ETHDSP.EthereumDataPrep 50 | */ 51 | const dataPrep = await dsp?.prepareData(yaml, await dsp.toPrepareParams(execParams, 'exec')) 52 | 53 | const state = await cleapi.executeOnDataPrep( 54 | { wasmUint8Array, cleYaml: yaml }, 55 | dataPrep as cleapi.DataPrep, 56 | ) 57 | 58 | expect(toHexString(state)).toEqual(expectedState) 59 | return state 60 | }, { timeout: 100000 }) 61 | 62 | it('test_exec_then_prove', async () => { 63 | const { wasmPath, yamlPath, expectedState, blocknum } = option 64 | 65 | /** 66 | * assemble cleExecutable & get dsp 67 | */ 68 | 69 | // get wasmUint8Array & Yaml 70 | const wasm = fs.readFileSync(wasmPath) 71 | const wasmUint8Array = new Uint8Array(wasm) 72 | // const yamlContent = fs.readFileSync(yamlPath, 'utf-8') 73 | const yaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 74 | const cleExecutable = { wasmUint8Array, cleYaml: yaml } 75 | // get dsp 76 | const dsp = cleapi.dspHub.getDSPByYaml(yaml) 77 | if (!dsp) 78 | throw new DSPNotFound('DSP not found') 79 | // get pre-defined test params 80 | const jsonRpcUrl = loadConfigByNetwork(yaml, config.JsonRpcProviderUrl, true) 81 | const provider = new providers.JsonRpcProvider(jsonRpcUrl) 82 | const generalParams = { 83 | provider, 84 | // blockId: loadConfigByNetwork(yaml, blocknumForStorageTest, true), // for storage 85 | blockId: loadConfigByNetwork(yaml, blocknum, true), // for event 86 | } 87 | 88 | /** 89 | * Execute 90 | */ 91 | 92 | // get exec params 93 | const execParams = dsp?.toExecParams(generalParams) 94 | 95 | // Prepare Data, can construct your own dataPrep based on this. 96 | // the actual dataPrep here is instance of cleapi.ETHDSP.EthereumDataPrep 97 | let dataPrep = await dsp?.prepareData(yaml, await dsp.toPrepareParams(execParams, 'exec')) 98 | 99 | const stateu8a = await cleapi.executeOnDataPrep( 100 | { wasmUint8Array, cleYaml: yaml }, 101 | dataPrep as cleapi.DataPrep, 102 | ) 103 | expect(toHexString(stateu8a)).toEqual(expectedState) 104 | 105 | const stateStr = cleapi.utils.toHexString(stateu8a) 106 | 107 | // /** 108 | // * the 2nd way to exec get state. 109 | // * gen private/public input (without expectedState) 110 | // */ 111 | // let input = new cleapi.Input(); 112 | // input = dsp.fillExecInput(input, yaml, dataPrep) 113 | // let [privateInputStr, publicInputStr] = [input.getPrivateInputStr(), input.getPublicInputStr()]; 114 | 115 | // console.log(`(Execute) Private Input: ${privateInputStr}`) 116 | // console.log(`(Execute) Public Input: ${publicInputStr}`) 117 | 118 | // // execute, get state 119 | // const state = await cleapi.executeOnInputs(cleExecutable, privateInputStr, publicInputStr) 120 | 121 | // console.log(`CLE STATE OUTPUT: ${stateStr}`) 122 | 123 | /** 124 | * Prove Input Gen 125 | */ 126 | 127 | dataPrep = dsp?.toProveDataPrep(dataPrep, stateStr) 128 | const input = cleapi.proveInputGenOnDataPrep(cleExecutable, dataPrep as cleapi.DataPrep) 129 | input 130 | // console.log(`(Prove) Private Input: ${input.getPrivateInputStr()}`) 131 | // console.log(`(Prove) Public Input: ${input.getPublicInputStr()}`) 132 | }, { timeout: 100000 }) 133 | }) 134 | -------------------------------------------------------------------------------- /tests/exec_test_impl.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { providers } from 'ethers' 3 | import { expect } from 'vitest' 4 | import { loadConfigByNetwork, toHexString } from '../src/common/utils' 5 | import * as cleapi from '../src/index' 6 | import { config } from './config' 7 | import { loadYamlFromPath } from './utils/yaml' 8 | 9 | export async function testExecute(option: any) { 10 | const { wasmPath, yamlPath, expectedState, blocknum } = option 11 | 12 | const wasm = fs.readFileSync(wasmPath) 13 | const wasmUint8Array = new Uint8Array(wasm) 14 | // const yamlContent = fs.readFileSync(yamlPath, 'utf-8') 15 | const yaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 16 | const dsp = cleapi.dspHub.getDSPByYaml(yaml, { }) 17 | // const dsp = cleapi.dspHub.getDSPByYaml(yaml, { isLocal: false }) 18 | 19 | const jsonRpcUrl = loadConfigByNetwork(yaml, config.JsonRpcProviderUrl, true) 20 | const provider = new providers.JsonRpcProvider(jsonRpcUrl) 21 | const generalParams = { 22 | provider, 23 | blockId: loadConfigByNetwork(yaml, blocknum, true), // for storage 24 | // blockId: loadConfigByNetwork(yaml, blocknumForEventTest, true), // for event 25 | } 26 | 27 | const execParams = dsp?.toExecParams(generalParams) 28 | 29 | const state = await cleapi.execute( 30 | { wasmUint8Array, cleYaml: yaml }, 31 | execParams as any, 32 | ) 33 | // console.log(toHexString(state)) 34 | 35 | expect(toHexString(state)).toEqual(expectedState) 36 | return state 37 | } 38 | -------------------------------------------------------------------------------- /tests/fixtures/compile/cle.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | apiVersion: 0.0.2 3 | name: eg_hello 4 | description: "This demo CLE always set a ascii string as the output state. " 5 | repository: https://github.com/ora-io/cle 6 | dataSources: 7 | - kind: ethereum 8 | network: sepolia 9 | event: 10 | - address: '0xFA5Db19087920F5d0e71d0373F099bd0C03589DA' 11 | events: 12 | - "Sync(uint112,uint112)" 13 | mapping: 14 | language: wasm/assemblyscript 15 | file: ./mapping.ts 16 | handler: handleBlocks 17 | dataDestinations: 18 | - kind: ethereum 19 | network: sepolia 20 | address: "0x0000000000000000000000000000000000000001" 21 | -------------------------------------------------------------------------------- /tests/fixtures/compile/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Block } from '@ora-io/cle-lib' 2 | import { Bytes } from '@ora-io/cle-lib' 3 | 4 | export function handleBlocks(blocks: Block[]): Bytes { 5 | return Bytes.fromUTF8('Hello CLE!') 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum(event)/cle.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | apiVersion: 0.0.2 3 | name: eg_addr 4 | description: 'Dev Test CLE. ' 5 | repository: https://github.com/ora-io/cle 6 | dataSources: 7 | - kind: ethereum 8 | network: sepolia 9 | # block: 10 | # - offset: [0] 11 | event: 12 | - address: '0x5c7a6cf20cbd3eef32e19b9cad4eca17c432a794' 13 | events: 14 | - 'SubmissionReceived(int256,uint32,address)' 15 | - '0x63754dc9539dda818b815ea60ecf3230584f27a98b815ea60ecf32309539dd84' 16 | 17 | mapping: 18 | language: wasm/assemblyscript 19 | file: ./mapping.ts 20 | handler: handleBlocks 21 | 22 | # dataDestinations: 23 | # - kind: ethereum 24 | # network: sepolia 25 | # address: '0x1B17C66e37CB33202Fd1C058fa1B97e36b7e517D' 26 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum(event)/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Block } from '@ora-io/cle-lib' 2 | import { Bytes } from '@ora-io/cle-lib' 3 | 4 | export function handleBlocks(blocks: Block[]): Bytes { 5 | return blocks[0].events[0].address 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum(storage)/cle.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | apiVersion: 0.0.2 3 | name: eg_addr 4 | description: 'Dev Test CLE. ' 5 | repository: https://github.com/ora-io/cle 6 | dataSources: 7 | - kind: ethereum 8 | network: sepolia 9 | # block: 10 | # - offset: [0] 11 | # event: 12 | # - address: '0x5c7a6cf20cbd3eef32e19b9cad4eca17c432a794' 13 | # events: 14 | # - 'SubmissionReceived(int256,uint32,address)' 15 | # - '0x63754dc9539dda818b815ea60ecf3230584f27a98b815ea60ecf32309539dd84' 16 | storage: 17 | - address: '0xa60ecf32309539dd84f27a9563754dca818b815e' 18 | slots: 19 | - 8 20 | - 9 21 | 22 | mapping: 23 | language: wasm/assemblyscript 24 | file: ./mapping.ts 25 | handler: handleBlocks 26 | 27 | # dataDestinations: 28 | # - kind: ethereum 29 | # network: sepolia 30 | # address: '0x1B17C66e37CB33202Fd1C058fa1B97e36b7e517D' 31 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum(storage)/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Block } from '@ora-io/cle-lib' 2 | import { Bytes } from '@ora-io/cle-lib' 3 | 4 | export function handleBlocks(blocks: Block[]): Bytes { 5 | return blocks[0].accounts[0].address 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum.unsafe-ethereum/cle.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | apiVersion: 0.0.2 3 | name: eg_addr 4 | description: 'Dev Test CLE. ' 5 | repository: https://github.com/ora-io/cle 6 | dataSources: 7 | - kind: ethereum 8 | unsafe: true 9 | network: sepolia 10 | # block: 11 | # - offset: [0] 12 | event: 13 | - address: '0x5c7a6cf20cbd3eef32e19b9cad4eca17c432a794' 14 | events: 15 | - 'SubmissionReceived(int256,uint32,address)' 16 | - '0x63754dc9539dda818b815ea60ecf3230584f27a98b815ea60ecf32309539dd84' 17 | # storage: 18 | # - address: '0xa60ecf32309539dd84f27a9563754dca818b815e' 19 | # slots: 20 | # - 8 21 | # - 9 22 | - kind: ethereum 23 | network: sepolia 24 | # block: 25 | # - offset: [0] 26 | event: 27 | - address: '0x5c7a6cf20cbd3eef32e19b9cad4eca17c432a794' 28 | events: 29 | - 'SubmissionReceived(int256,uint32,address)' 30 | - '0x63754dc9539dda818b815ea60ecf3230584f27a98b815ea60ecf32309539dd84' 31 | 32 | mapping: 33 | language: wasm/assemblyscript 34 | file: ./mapping.ts 35 | handler: handleBlocks 36 | 37 | # dataDestinations: 38 | # - kind: ethereum 39 | # network: sepolia 40 | # address: '0x1B17C66e37CB33202Fd1C058fa1B97e36b7e517D' 41 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum.unsafe-ethereum/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Block, ByteArray } from '@ora-io/cle-lib' 2 | import { Bytes } from '@ora-io/cle-lib' 3 | 4 | export function handleBlocks(blocksUnsafe: Block[], blocks: Block[]): Bytes { 5 | return blocksUnsafe[0].events[0].address.concat(ByteArray.fromI32(0)).concat(blocks[0].events[0].address) as Bytes 6 | // return blocksUnsafe[0].accountByHexString('0xa60ecf32309539dd84f27a9563754dca818b815e').storageByI32(8) 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum.unsafe/cle.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.2 2 | apiVersion: 0.0.2 3 | name: eg_addr 4 | description: 'Dev Test CLE. ' 5 | repository: https://github.com/ora-io/cle 6 | dataSources: 7 | - kind: ethereum 8 | unsafe: true 9 | network: sepolia 10 | # block: 11 | # - offset: [0] 12 | event: 13 | - address: '0x5c7a6cf20cbd3eef32e19b9cad4eca17c432a794' 14 | events: 15 | - 'SubmissionReceived(int256,uint32,address)' 16 | - '0x63754dc9539dda818b815ea60ecf3230584f27a98b815ea60ecf32309539dd84' 17 | 18 | mapping: 19 | language: wasm/assemblyscript 20 | file: ./mapping.ts 21 | handler: handleBlocks 22 | 23 | # dataDestinations: 24 | # - kind: ethereum 25 | # network: sepolia 26 | # address: '0x1B17C66e37CB33202Fd1C058fa1B97e36b7e517D' 27 | -------------------------------------------------------------------------------- /tests/fixtures/dsp/ethereum.unsafe/mapping.ts: -------------------------------------------------------------------------------- 1 | import { Block } from '@ora-io/cle-lib' 2 | import { Bytes } from '@ora-io/cle-lib' 3 | 4 | export function handleBlocks(blocksUnsafe: Block[]): Bytes { 5 | return blocksUnsafe[0].events[0].address 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixureoptions.ts: -------------------------------------------------------------------------------- 1 | import type { NetworksConfig } from '../src/common/utils' 2 | import { config } from './config' 3 | // for storage case 4 | import { getLatestBlocknumber } from './utils/ethers' 5 | 6 | interface OptionType { 7 | mappingPath: string 8 | yamlPath: string 9 | wasmPath: string 10 | watPath: string 11 | local: boolean 12 | expectedState: string 13 | blocknum: NetworksConfig 14 | } 15 | 16 | const defaultPath = (pathFromFixtures: string) => { 17 | return { 18 | mappingPath: `tests/fixtures/${pathFromFixtures}/mapping.ts`, 19 | yamlPath: `tests/fixtures/${pathFromFixtures}/cle.yaml`, 20 | wasmPath: `tests/fixtures/build/${pathFromFixtures}.wasm`, 21 | watPath: `tests/fixtures/build/${pathFromFixtures}.wat`, 22 | } 23 | } 24 | const latestblocknum = { 25 | sepolia: await getLatestBlocknumber(config.JsonRpcProviderUrl.sepolia) - 1, // -1: allow a cache time on rpc side 26 | mainnet: await getLatestBlocknumber(config.JsonRpcProviderUrl.mainnet) - 1, 27 | } 28 | // for event case 29 | const eventblocknum = { 30 | sepolia: 2279547, // to test event use 2279547, to test storage use latest blocknum 31 | mainnet: 17633573, 32 | } 33 | 34 | export const fixtures: Record = { 35 | 'dsp/ethereum(storage)': { 36 | ...defaultPath('dsp/ethereum(storage)'), 37 | local: false, 38 | expectedState: 'a60ecf32309539dd84f27a9563754dca818b815e', // storage case 39 | blocknum: latestblocknum, 40 | }, 41 | 'dsp/ethereum(event)': { 42 | ...defaultPath('dsp/ethereum(event)'), 43 | local: false, 44 | expectedState: '5c7a6cf20cbd3eef32e19b9cad4eca17c432a794', // event case 45 | blocknum: eventblocknum, 46 | }, 47 | 'dsp/ethereum.unsafe': { 48 | ...defaultPath('dsp/ethereum.unsafe'), 49 | local: false, 50 | expectedState: '5c7a6cf20cbd3eef32e19b9cad4eca17c432a794', // event case 51 | blocknum: eventblocknum, 52 | }, 53 | 'dsp/ethereum.unsafe-ethereum': { 54 | ...defaultPath('dsp/ethereum.unsafe-ethereum'), 55 | local: false, 56 | expectedState: '5c7a6cf20cbd3eef32e19b9cad4eca17c432a794000000005c7a6cf20cbd3eef32e19b9cad4eca17c432a794', // use event return 57 | blocknum: eventblocknum, 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /tests/prove.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { ethers, providers } from 'ethers' 3 | import { describe, expect, it } from 'vitest' 4 | import { loadConfigByNetwork } from '../src/common/utils' 5 | import * as cleapi from '../src/index' 6 | import { config } from './config' 7 | import { loadYamlFromPath } from './utils/yaml' 8 | import { fixtures } from './fixureoptions' 9 | 10 | (global as any).__BROWSER__ = false 11 | 12 | const pathfromfixtures = 'dsp/ethereum(storage)' 13 | const option = fixtures[pathfromfixtures] 14 | 15 | describe(`test prove ${pathfromfixtures}`, () => { 16 | // console.log('issued a prove taslk: ', result) 17 | it('test mock mode', async () => { 18 | const { yamlPath, wasmPath, blocknum, expectedState } = option 19 | 20 | const wasm = fs.readFileSync(wasmPath) 21 | const wasmUint8Array = new Uint8Array(wasm) 22 | // const yamlContent = fs.readFileSync(yamlPath, 'utf-8') 23 | const yaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 24 | const dsp = cleapi.dspHub.getDSPByYaml(yaml) 25 | const jsonRpcUrl = loadConfigByNetwork(yaml, config.JsonRpcProviderUrl, true) 26 | const provider = new providers.JsonRpcProvider(jsonRpcUrl) 27 | const generalParams = { 28 | provider, 29 | blockId: loadConfigByNetwork(yaml, blocknum, true), 30 | expectedStateStr: expectedState, 31 | } 32 | 33 | const proveParams = dsp?.toProveParams(generalParams) 34 | const input = await cleapi.proveInputGen( 35 | { cleYaml: yaml }, // doesn't care about wasmUint8Array 36 | proveParams as any, 37 | ) 38 | 39 | console.log(input.auxParams) 40 | 41 | const res = await cleapi.proveMock( 42 | { wasmUint8Array }, 43 | input, 44 | ) 45 | console.log('mock result:', res) 46 | }, { timeout: 100000 }) 47 | it.only('test prove mode', async () => { 48 | const { wasmPath, yamlPath, blocknum, expectedState } = option 49 | const wasm = fs.readFileSync(wasmPath) 50 | const wasmUint8Array = new Uint8Array(wasm) 51 | const yaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 52 | 53 | const dsp = cleapi.dspHub.getDSPByYaml(yaml) 54 | 55 | const jsonRpcUrl = loadConfigByNetwork(yaml, config.JsonRpcProviderUrl, true) 56 | const provider = new providers.JsonRpcProvider(jsonRpcUrl) 57 | const generalParams = { 58 | provider, 59 | blockId: loadConfigByNetwork(yaml, blocknum, true), 60 | expectedStateStr: expectedState, 61 | } 62 | 63 | const proveParams = dsp?.toProveParams(generalParams) 64 | 65 | const input = await cleapi.proveInputGen( 66 | { cleYaml: yaml }, // doesn't care about wasmUint8Array 67 | proveParams as any, 68 | ) 69 | 70 | // console.log(privateInputStr); 71 | // console.log("-------------------"); 72 | // console.log(publicInputStr); 73 | const userPrivateKey = config.UserPrivateKey 74 | const signer = new ethers.Wallet(userPrivateKey, provider) 75 | 76 | const result = await cleapi.requestProve( 77 | { wasmUint8Array }, // doesn't care about cleYaml 78 | input, 79 | { 80 | proverUrl: config.ZkwasmProviderUrl, 81 | signer, 82 | batchStyle: cleapi.BatchStyle.ZKWASMHUB, 83 | }) 84 | 85 | console.log(result) 86 | expect(result.taskId).toBeTypeOf('string') 87 | }, { timeout: 100000 }) 88 | 89 | it('test waitProve', async () => { 90 | const taskId = '65dae256429af08ed922479a' 91 | const result = await cleapi.waitProve(config.ZkwasmProviderUrl, taskId as string, { batchStyle: cleapi.BatchStyle.ZKWASMHUB }) 92 | // console.log(result.proofParams?.instances) 93 | expect((result.proofParams?.instances as any[])[0]).toBeInstanceOf(Array) 94 | }, { timeout: 100000 }) 95 | }) 96 | -------------------------------------------------------------------------------- /tests/publish.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { Contract, ethers } from 'ethers' 3 | import { expect, it } from 'vitest' 4 | import { CLEAlreadyExist } from '../src/common/error' 5 | import * as cleapi from '../src/index' 6 | import { abiFactory, addressFactory, cleContractABI } from '../src/common/constants' 7 | import { config } from './config' 8 | import { loadYamlFromPath } from './utils/yaml' 9 | import { fixtures } from './fixureoptions' 10 | 11 | (global as any).__BROWSER__ = false 12 | 13 | const pathfromfixtures = 'dsp/ethereum(storage)' 14 | // const pathfromfixtures = 'dsp/ethereum.unsafe-ethereum' 15 | const option = fixtures[pathfromfixtures] 16 | 17 | // enable this to silence logs 18 | // cleapi.setCLELogger(new cleapi.SilentLogger()) 19 | 20 | it.skip('test publish', async () => { 21 | const { wasmPath, yamlPath } = option 22 | const cleYaml = loadYamlFromPath(yamlPath) as cleapi.CLEYaml 23 | const network = cleYaml.decidePublishNetwork() 24 | console.log('network', network) 25 | expect(network).toBeDefined() 26 | if (network === undefined) 27 | throw new Error('network is undefined') 28 | 29 | const rpcUrl = (config.JsonRpcProviderUrl as any)[network] 30 | 31 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 32 | const userPrivateKey = config.UserPrivateKey 33 | const signer = new ethers.Wallet(userPrivateKey, provider) 34 | const ipfsHash = Math.floor(Math.random() * (100000 - 0 + 1)).toString() 35 | const newBountyRewardPerTrigger = 0.01 36 | const wasm = fs.readFileSync(wasmPath) 37 | const wasmUint8Array = new Uint8Array(wasm) 38 | try { 39 | // proverUrl?: string, 40 | // ipfsHash: string, 41 | // bountyRewardPerTrigger: number, 42 | const publishTxHash = await cleapi.publish( 43 | { wasmUint8Array, cleYaml }, 44 | signer, 45 | { proverUrl: config.ZkwasmProviderUrl, ipfsHash, bountyRewardPerTrigger: newBountyRewardPerTrigger }, 46 | ) 47 | 48 | console.log('publishTxHash:', publishTxHash) 49 | // slice the last 40 digits of result.logs[2].data to a ethereum address 50 | // const address = "0x" + publishTxHash.logs[2].data.slice(-40); 51 | 52 | // console.log(address); 53 | 54 | const factoryContract = new Contract(addressFactory.sepolia, abiFactory, signer) 55 | const deployedAddress = await factoryContract.getCLEBycreator(signer.address) 56 | 57 | const cleContract = new Contract(deployedAddress[deployedAddress.length - 1], cleContractABI, provider).connect(signer) 58 | 59 | const reward = await cleContract.bountyReward() 60 | // expect reward is equal to newBountyRewardPerTrigger 61 | expect(reward).toEqual(ethers.utils.parseEther(newBountyRewardPerTrigger.toString())) 62 | } 63 | catch (error) { 64 | if (error instanceof CLEAlreadyExist) 65 | console.error('CLE already exist') 66 | else 67 | throw error 68 | } 69 | }, { timeout: 100000 }) 70 | -------------------------------------------------------------------------------- /tests/rlp.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { ethers } from 'ethers' 3 | import { getBlock } from '../src/common/ethers_helper' 4 | import { BlockPrep } from '../src/dsp/ethereum/blockprep' 5 | import { config } from './config' 6 | 7 | (global as any).__BROWSER__ = false 8 | 9 | describe('test rlp', async () => { 10 | it('rlp', async () => { 11 | const rpcUrl = config.JsonRpcProviderUrl.sepolia 12 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 13 | 14 | const rawblock = await getBlock(provider, 5016668) 15 | 16 | const rlp = (new BlockPrep(rawblock)).rlpheader 17 | expect(rlp).equal('f90241a0025974921a97becf37f7fdc29e0811a049892424aa28e05b439c8aff2b8d9c3ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347941e2cd78882b12d3954a049fd82ffd691565dc0a5a0dcce553b95933feeb544064ac6d625d14f2cade14af0417bf7385aa26e28d29ca0c7f658355f6a8b6e3b22a1ff80d8609ff947bf62bb09883fcb2248c7655b4f12a0e40b0d566247b29459e36ad0b3555eb55582ef9fa914cb6681017502b26f6f9db9010000089500ec08400002581681021040c482130013020410400510208020820024d00c20421403489c08098218170a7c408880c0822630100082082820602a8470c108128000a100040800c70888c800a0020400028a4212006402002210002402280c021852820080804693a420108d0000210f8040110030000412121a042e040ea018120065102028e04880c1c0488d10000800026481530c041a0004411e050a3904150874005246081c4608500d001020a004642880200089018a0c02040040440123010051020904a4a422806040020104810169004140008b005084a052939100982900005000406200a2242824c9008054008a0010a24809808280151880834c8c5c8401c9c3808401068b17846595fa949f496c6c756d696e61746520446d6f63726174697a6520447374726962757465a01d632c68a5c589f1b4878bd582684b2f9dbb280a6cc275ab65ed80a39c9673ad880000000000000000840ac2e756a0b5c002c11d1eb3deb01d03ec77444bca11267b889b48af4cb378443841cd1225') 18 | 19 | // console.log(rlp) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/setup.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { describe, it } from 'vitest' 3 | import { ethers, providers } from 'ethers' 4 | import * as cleapi from '../src/index' 5 | import { config } from './config' 6 | import { fixtures } from './fixureoptions' 7 | 8 | (global as any).__BROWSER__ = false 9 | 10 | const pathfromfixtures = 'dsp/ethereum(storage)' 11 | const option = fixtures[pathfromfixtures] 12 | 13 | describe('test setup', () => { 14 | it('test setup', async () => { 15 | const { wasmPath } = option 16 | const wasm = fs.readFileSync(wasmPath) 17 | const wasmUint8Array = new Uint8Array(wasm) 18 | const provider = new providers.JsonRpcProvider('http://localhost') // not important 19 | const signer = new ethers.Wallet(config.UserPrivateKey, provider) 20 | const result = await cleapi.setup( 21 | { wasmUint8Array }, 22 | { circuitSize: 22, proverUrl: config.ZkwasmProviderUrl, signer }, 23 | ) 24 | result 25 | // console.log('test result', result) 26 | }, { timeout: 10000 }) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/test_zkwasm_setup-usinghelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is modified from https://github.com/DelphinusLab/zkWasm-service-helper#a-example-to-add-new-wasm-image. save for future work. 3 | * can delete after setup stable. 4 | */ 5 | 6 | // import fs from 'fs' 7 | import { Wallet } from 'ethers' 8 | import { computeAddress } from 'ethers/lib/utils.js' 9 | import { 10 | // AddImageParams, 11 | // WithSignature, 12 | ZkWasmServiceHelper, 13 | ZkWasmUtil, 14 | } from '@ora-io/zkwasm-service-helper' 15 | 16 | async function signMessage(message, sk) { 17 | const wallet = new Wallet(sk) 18 | return await wallet.signMessage(message) 19 | } 20 | 21 | export async function zkwasm_setup( 22 | ZkwasmProviderUrl, 23 | name, 24 | image_md5, 25 | image, 26 | user_privatekey, 27 | description_url, 28 | iconURL, 29 | circuitSize, 30 | ) { 31 | const helper = new ZkWasmServiceHelper(ZkwasmProviderUrl, '', '') 32 | // const imagePath = 'tests/build/cle-storage.wasm' 33 | 34 | // const image = createFileFromUint8Array(wasmUint8Array, wasmName); 35 | // const fileSelected = fs.readFileSync(imagePath) 36 | // Given by the caller for md5 reuse purpose. 37 | // const md5 = ZkWasmUtil.convertToMd5( 38 | // fileSelected, 39 | // ) 40 | 41 | const info = { 42 | name, 43 | image_md5: image_md5.toLowerCase(), // fix compatible issue with new zkwasm explorer. 44 | image, 45 | user_address: computeAddress(user_privatekey).toLowerCase(), 46 | description_url, 47 | avator_url: iconURL, 48 | circuit_size: circuitSize, 49 | } 50 | 51 | const msg = ZkWasmUtil.createAddImageSignMessage(info) 52 | const signature = await signMessage(msg, user_privatekey) // Need user private key to sign the msg 53 | const task = { 54 | ...info, 55 | signature, 56 | } 57 | 58 | const response = (await helper.addNewWasmImage(task)) 59 | 60 | return response 61 | } 62 | 63 | -------------------------------------------------------------------------------- /tests/trigger.test.ts: -------------------------------------------------------------------------------- 1 | import { describe } from 'node:test' 2 | import { it } from 'vitest' 3 | import { ethers } from 'ethers' 4 | import * as cleapi from '../src/index' 5 | import { loadYamlFromPath } from './utils/yaml' 6 | import { config } from './config' 7 | import { fixtures } from './fixureoptions' 8 | 9 | (global as any).__BROWSER__ = false 10 | 11 | // const rpcUrl = 'https://rpc.ankr.com/eth_sepolia' 12 | 13 | const pathfromfixtures = 'dsp/ethereum(storage)' 14 | // const pathfromfixtures = 'dsp/ethereum.unsafe-ethereum' 15 | const option = fixtures[pathfromfixtures] 16 | // let ZkwasmProviderUrl = "https://zkwasm-explorer.delphinuslab.com:8090" 17 | const proveTaskId = '65dd7dad235cd47b5193efce' // true 18 | // const proveTaskId = '655568eaadb2c56ffd2f0ee0' // fasle 19 | 20 | // TODO: use a reward == 0 cle to pass trigger test 21 | 22 | describe('test trigger', () => { 23 | it('eth ddp', async () => { 24 | const { yamlPath } = option 25 | const yaml = loadYamlFromPath(yamlPath) 26 | 27 | const proofParams = await cleapi.getVerifyProofParamsByTaskID(config.ZkwasmProviderUrl, proveTaskId) 28 | const CLEID = '0x8fd9e85b23d3777993ebf04ad3a3b0878f7fee77' 29 | const userPrivateKey = config.UserPrivateKey 30 | const rpcUrl = config.JsonRpcProviderUrl.sepolia 31 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 32 | const signer = new ethers.Wallet(userPrivateKey, provider) 33 | const ddpParams = { signer, gasLimit: 10000000, onlyMock: true } 34 | await cleapi.trigger( 35 | { cleYaml: yaml }, 36 | CLEID, 37 | proofParams, 38 | [ddpParams], // 1 ddpParams per ddp 39 | ) 40 | }, 100000) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { describe, expect, it } from 'vitest' 3 | import { ethers } from 'ethers' 4 | import { ZkWasmUtil } from '@ora-io/zkwasm-service-helper' 5 | import yaml from 'js-yaml' 6 | import * as cleapi from '../src/index' 7 | import { u32ListToUint8Array } from '../src/common/utils' 8 | import { config } from './config' 9 | import { loadYamlFromPath } from './utils/yaml' 10 | import { fixtures } from './fixureoptions' 11 | 12 | (global as any).__BROWSER__ = false 13 | 14 | describe('test utils', () => { 15 | it('test healthcheck', () => { 16 | test_healthcheck(fixtures['dsp/ethereum(storage)'].yamlPath) 17 | test_healthcheck(fixtures['dsp/ethereum(event)'].yamlPath) 18 | }) 19 | it('getRawReceipts', async () => { 20 | const provider = new ethers.providers.JsonRpcProvider(config.JsonRpcProviderUrl.sepolia) 21 | 22 | const res = await cleapi.getRawReceipts(provider, 4818711, false) 23 | res 24 | // console.log(res) 25 | }, { timeout: 100000 }) 26 | 27 | it('u32ListToUint8Array', () => { 28 | const blocknums = [5353087, 5353088] 29 | const result = u32ListToUint8Array(blocknums, 32) 30 | // console.log('test u32ListToUint8Array', result) 31 | const extra = ZkWasmUtil.bytesToBigIntArray(result) 32 | console.log('test u32ListToUint8Array extra', extra) 33 | // expect(result).equals(new Uint8Array([ 127, 174, 81, 0 ])) // imcomplete 34 | }) 35 | 36 | it('yaml filterSections', () => { 37 | const yaml = loadYamlFromPath(fixtures['dsp/ethereum(event)'].yamlPath) as any 38 | expect(yaml.dataSources[0].filterByKeys(['event', 'storage']).event).toBeInstanceOf(Array) 39 | }) 40 | it('yaml toString', () => { 41 | const yamlpath = fixtures['dsp/ethereum(event)'].yamlPath 42 | const yamlContents = fs.readFileSync(yamlpath, 'utf8') 43 | const expectedYamlDump = yaml.dump(yaml.load(yamlContents)) 44 | 45 | const yamlObj = loadYamlFromPath(yamlpath) as any 46 | 47 | expect(yamlObj.toString()).equals(expectedYamlDump) 48 | }) 49 | }) 50 | 51 | function test_healthcheck(yamlPath: string) { 52 | try { 53 | const yaml1 = loadYamlFromPath(yamlPath) as any 54 | cleapi.CLEYaml.healthCheck(yaml1) 55 | console.log('valid:', yamlPath) 56 | } 57 | catch (e) { 58 | console.log(e) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/utils/ethers.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | 3 | export async function getLatestBlocknumber(rpcUrl: string) { 4 | const provider = new ethers.providers.JsonRpcProvider(rpcUrl) 5 | const block = await provider.getBlock('latest') 6 | return block.number 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/utils/file.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import os from 'os' 3 | import fs from 'fs' 4 | 5 | export function createFileFromUint8Array(array: string | NodeJS.ArrayBufferView, fileName: string) { 6 | const tempDir = os.tmpdir() 7 | const filePath = path.join(tempDir, fileName) 8 | fs.writeFileSync(filePath, array as any) 9 | return fs.createReadStream(filePath) 10 | } 11 | 12 | export function createOnNonexist(filePath: string): void { 13 | const directoryPath = path.dirname(filePath) 14 | 15 | if (!fs.existsSync(directoryPath)) 16 | fs.mkdirSync(directoryPath, { recursive: true }) 17 | } 18 | -------------------------------------------------------------------------------- /tests/utils/yaml.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { CLEYaml } from '../../src' 3 | export function loadYamlFromPath(path: string) { 4 | const fileContents = fs.readFileSync(path, 'utf8') 5 | return CLEYaml.fromYamlContent(fileContents) 6 | } 7 | -------------------------------------------------------------------------------- /tests/verify.test.ts: -------------------------------------------------------------------------------- 1 | import { describe } from 'node:test' 2 | import { expect, it } from 'vitest' 3 | import { ethers } from 'ethers' 4 | import * as cleapi from '../src/index' 5 | import { AggregatorVerifierAddress } from '../src/common/constants' 6 | import { loadYamlFromPath } from './utils/yaml' 7 | import { fixtures } from './fixureoptions' 8 | import { config } from './config' 9 | 10 | (global as any).__BROWSER__ = false 11 | 12 | const yamlPath = fixtures['dsp/ethereum(event)'].yamlPath 13 | // const proveTaskId = 'v4YpdX4UufG89z2CwA26m0OS' // ora prover proof 14 | const proveTaskId = '65dd7dad235cd47b5193efce' // zkwasmhub proof 15 | 16 | describe('test verify', () => { 17 | const cleYaml = loadYamlFromPath(yamlPath) 18 | 19 | it('test verify CLEExecutable', async () => { 20 | const verifyParams = await cleapi.getVerifyProofParamsByTaskID(config.ZkwasmProviderUrl, proveTaskId) 21 | 22 | const network = cleYaml.decidePublishNetwork() 23 | expect(network).toBeDefined() 24 | if (network === undefined) 25 | throw new Error('network is undefined') 26 | 27 | // const verifierContractAddress = loadConfigByNetwork(cleYaml as CLEYaml, AggregatorVerifierAddress, false) 28 | const verifierAddress = (AggregatorVerifierAddress as any)[network] 29 | expect(await cleapi.verify( 30 | verifyParams, 31 | { verifierAddress, provider: new ethers.providers.JsonRpcProvider(config.JsonRpcProviderUrl[network]) }, 32 | )).toBeTruthy() 33 | }) 34 | // 2nd way to verify proof. 35 | it.only('test verify proof params', async () => { 36 | const proofParams = await cleapi.getVerifyProofParamsByTaskID(config.ZkwasmProviderUrl, proveTaskId) 37 | const network = 'sepolia' 38 | // const verifierAddress = '0x9B13520f499e95f7e94E8346Ed8F52D2F830d955' // ora verifier 39 | // const verifierAddress = '0xfD74dce645Eb5EB65D818aeC544C72Ba325D93B0' // zkwasmhub verifier 40 | 41 | expect(await cleapi.verifyProof( 42 | proofParams, 43 | { 44 | // verifierAddress, 45 | provider: new ethers.providers.JsonRpcProvider(config.JsonRpcProviderUrl[network]), 46 | // batchStyle: cleapi.BatchStyle.ORA_SINGLE 47 | }, 48 | )).toBeTruthy() 49 | 50 | /// / make a wrong proof for test 51 | proofParams.aggregate_proof[0] = 0x12 52 | expect(await cleapi.verifyProof( 53 | proofParams, 54 | { 55 | // verifierAddress, 56 | provider: new ethers.providers.JsonRpcProvider(config.JsonRpcProviderUrl[network]), 57 | // batchStyle: cleapi.BatchStyle.ORA_SINGLE 58 | }, 59 | )).toBeFalsy() 60 | }, { 61 | timeout: 1000000, 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "esnext", 5 | "lib": ["esnext", "DOM"], 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "skipDefaultLibCheck": true 13 | }, 14 | "include": ["src/**/*", "test/**/*", "types/**/*"], 15 | "exclude": ["dist", "test/fixtures/**/*", "node_modules"] 16 | } 17 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Global compile-time constants 2 | declare let __BROWSER__: boolean 3 | --------------------------------------------------------------------------------