-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5380581
commit fbd3837
Showing
7 changed files
with
1,151 additions
and
542 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,18 @@ | |
This repository contains a set of utilities for testing Mina zkapps: | ||
|
||
- Custom jest matchers | ||
- An interactive debugger to interact with locally deployed contracts | ||
- A contract (gates) sizer | ||
|
||
# Requirements | ||
|
||
- `node +18` (nvm recommended) | ||
- `[email protected].*` | ||
|
||
# Installing | ||
|
||
In order to use this tool, you should install it as a dev dependency on your existing project, for example, one created using `zkcli-app`: | ||
|
||
```bash | ||
npm --save-dev install mina-jest-matchers #TODO | ||
``` | ||
|
@@ -65,7 +74,7 @@ export class Add extends SmartContract { | |
``` | ||
```bash | ||
❯ npx mina-testing-utils | ||
❯ npx mina-testing-utils repl | ||
|
||
|
||
███╗ ███╗ ██╗ ███╗ ██╗ █████╗ ████████╗ ███████╗ ███████╗ ████████╗ ██╗ ███╗ ██╗ ██████╗ ██╗ ██╗ ████████╗ ██╗ ██╗ ███████╗ | ||
|
@@ -83,13 +92,10 @@ Please load the Mina REPL context by executing .loadMina before running any comm | |
|
||
|
||
|
||
mina-testing-utils> .load | ||
load loadMina | ||
|
||
mina-testing-utils> .loadMina | ||
✔ Snarky loaded successfully! You can access it through the mina object. | ||
|
||
mina-testing-utils> let { Add } = await import("/Users/alan.verbner/Projects/globant/mina/04-zkapp-browser-ui/contracts/build/src/Add.js") | ||
mina-testing-utils> let { Add } = await import("/Projects/yourProject/build/src/Add.js") | ||
|
||
mina-testing-utils> let { priv: zkAppPrivateKey, pub: zkAppAddress } = mina.genKeyPair(); | ||
|
||
|
@@ -113,6 +119,20 @@ See it in action: | |
[![asciicast](https://asciinema.org/a/603288.svg)](https://asciinema.org/a/603288) | ||
## Using the contract sizer | ||
This tool also allows you to keep track of the amount of gates the contracts you create are creating thus giving an idea of the complexity it will involve using them. To try it, run: | ||
```bash | ||
npx mina-testing-utils circuits-sizer build/src/test-contract.js | ||
``` | ||
Bear in mind you need to compile them first (if `zkapp-cli` is being used, it should be `npm run build`). | ||
See it in action: | ||
[![asciicast](https://asciinema.org/a/13DtDxa6nId5AtEhDcvZ3IyDf.svg)](https://asciinema.org/a/13DtDxa6nId5AtEhDcvZ3IyDf) | ||
# Development | ||
## Requirements | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const Table = require("cli-table"); | ||
const Spinnies = require("spinnies"); | ||
|
||
const spinnies = new Spinnies({ spinner: { frames: ["⚙️"] } }); | ||
|
||
/** | ||
* Checks if the provided path is valid. | ||
* @param {string} inputPath - The path to check. | ||
* @returns {boolean} - Returns true if the path is valid, false otherwise. | ||
*/ | ||
function isValidPath(inputPath) { | ||
if (!fs.existsSync(inputPath)) { | ||
console.error(`${inputPath} does not exist.`); | ||
return false; | ||
} | ||
|
||
const stats = fs.statSync(inputPath); | ||
if (!stats.isFile()) { | ||
console.error(`${inputPath} is not a file.`); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Processes a contract. | ||
* @param {string} contractName - The name of the contract. | ||
* @param {object} contract - The contract object. | ||
* @returns {Array} - Returns an array of methods. | ||
*/ | ||
async function tryProcessContract(contractName, contract) { | ||
try { | ||
spinnies.add(contractName, { | ||
text: `Compiling ${contractName}`, | ||
}); | ||
await contract.compile(); | ||
spinnies.succeed(contractName, { text: `Compiling ${contractName}` }); | ||
const methods = []; | ||
Object.keys(contract._methodMetadata).map((key) => { | ||
methods.push([ | ||
contractName, | ||
key, | ||
contract._methodMetadata[key].gates.length, | ||
]); | ||
}); | ||
return methods; | ||
} catch (err) { | ||
spinnies.fail(conractName, { | ||
text: `Error while compiling ${contractName}`, | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Processes contracts from a file. | ||
* @param {string} inputPath - The path to the file. | ||
* @returns {Array} - Returns an array of rows. | ||
*/ | ||
async function tryProcessContractsFromFile(inputPath) { | ||
const rows = []; | ||
try { | ||
const contracts = await import(inputPath); | ||
|
||
if (contracts.length == 0) | ||
console.error(`No contract founds in ${inputPath}`); | ||
|
||
return Promise.all( | ||
Object.keys(contracts).map((contractName) => { | ||
const contract = contracts[contractName]; | ||
if (!contract.compile) return []; | ||
return tryProcessContract(contractName, contract); | ||
}) | ||
); | ||
} catch (err) { | ||
console.log(err); | ||
console.error( | ||
`Couldn't import ${inputPath}, please check if it's a valid compiled contract.` | ||
); | ||
} | ||
|
||
return rows; | ||
} | ||
|
||
/** | ||
* Main function to process files. | ||
* @param {Array} files - The array of file paths. | ||
*/ | ||
module.exports = async (files) => { | ||
const rows = ( | ||
await Promise.all( | ||
files.map(async (inputPath) => { | ||
if (!path.isAbsolute(inputPath)) { | ||
inputPath = path.resolve(inputPath); | ||
} | ||
if (!isValidPath(inputPath)) return []; | ||
return await tryProcessContractsFromFile(inputPath); | ||
}) | ||
) | ||
).flat(2); | ||
|
||
if (rows.length == 0) { | ||
console.warn("No contracts were found"); | ||
} else { | ||
const table = new Table({ | ||
head: ["Contract", "Method", "Gates"], | ||
rows: rows.sort((a, b) => b[2] - a[2]), | ||
}); | ||
|
||
console.log(table.toString()); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,29 @@ | ||
#!/usr/bin/env node --experimental-vm-modules --experimental-wasm-threads | ||
|
||
const chalk = require("chalk"); | ||
const { Command } = require("commander"); | ||
const { name, description, version } = require("../package.json"); | ||
const repl = require("./repl.cjs"); | ||
const { printBanner } = require("./gui.cjs"); | ||
const configureRepl = require("./repl.cjs"); | ||
const circuitsSizer = require("./circuits-sizer.cjs"); | ||
|
||
printBanner(); | ||
const program = new Command(); | ||
|
||
/** | ||
* Generates a key pair using snarkyjs. | ||
* @param {Object} snarkyjs - The snarkyjs library. | ||
* @returns {Object} - An object containing the private and public keys. | ||
*/ | ||
function genKeyPair(snarkyjs) { | ||
return () => { | ||
const priv = snarkyjs.PrivateKey.random(); | ||
const pub = priv.toPublicKey(); | ||
program.name(name).description(description).version(version); | ||
|
||
return { | ||
priv, | ||
pub, | ||
}; | ||
}; | ||
} | ||
program | ||
.command("repl") | ||
.description("Runs Mina Testing Utils repl") | ||
.action((str, options) => { | ||
printBanner(); | ||
repl(); | ||
}); | ||
|
||
/** | ||
* Dynamically imports a module from an absolute path. | ||
* @param {string} absolutePath - The absolute path of the module to import. | ||
* @returns {Promise} - A promise that resolves to the imported module. | ||
*/ | ||
function dynamicImport(absolutePath) { | ||
return import(absolutePath); | ||
} | ||
program | ||
.command("circuits-sizer") | ||
.description("Analyzes contracts gates amount") | ||
.argument("<files...>", "Specify the file") | ||
.action((files) => { | ||
return circuitsSizer(files); | ||
}); | ||
|
||
// Start a REPL session with the given options. | ||
configureRepl(dynamicImport, genKeyPair); | ||
program.parse(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.