Skip to content

Commit

Permalink
Merge pull request #6 from gonzalobustosglob/feat/cli
Browse files Browse the repository at this point in the history
feat: cli feature initial commit. Basic CLI support is provided.
  • Loading branch information
AlanVerbner authored Aug 22, 2023
2 parents 2f33407 + 21888e8 commit 756a5d0
Show file tree
Hide file tree
Showing 8 changed files with 929 additions and 68 deletions.
14 changes: 14 additions & 0 deletions 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
feat: cli feature initial commit. Basic CLI support is provided.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Tue Aug 15 15:18:29 2023 -0300
#
# On branch feat/cli
# Changes to be committed:
# new file: cli/cli.cjs
# modified: package-lock.json
# modified: package.json
# new file: tsconfig.lib.json
#
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,87 @@ describe('my test', () => {
You can refer to [contract.test.ts](./test/contract/test-contract.ts) for a running example.
## Using the interactive debugger
After installing the package, you can run the interactive debugger as follows:
```typescript
import { Field, SmartContract, state, State, method } from 'snarkyjs';

/**
* Basic Example
* See https://docs.minaprotocol.com/zkapps for more info.
*
* The Add contract initializes the state variable 'num' to be a Field(1) value by default when deployed.
* When the 'update' method is called, the Add contract adds Field(2) to its 'num' contract state.
*
* This file is safe to delete and replace with your own contract.
*/
export class Add extends SmartContract {
@state(Field) num = State<Field>();

init() {
super.init();
this.num.set(Field(1));
}

@method update() {
const currentState = this.num.getAndAssertEquals();
const newState = currentState.add(2);
this.num.set(newState);
}
}
```
```bash
npx mina-testing-utils


███╗ ███╗ ██╗ ███╗ ██╗ █████╗ ████████╗ ███████╗ ███████╗ ████████╗ ██╗ ███╗ ██╗ ██████╗ ██╗ ██╗ ████████╗ ██╗ ██╗ ███████╗
████╗ ████║ ██║ ████╗ ██║ ██╔══██╗ ╚══██╔══╝ ██╔════╝ ██╔════╝ ╚══██╔══╝ ██║ ████╗ ██║ ██╔════╝ ██║ ██║ ╚══██╔══╝ ██║ ██║ ██╔════╝
██╔████╔██║ ██║ ██╔██╗ ██║ ███████║ ██║ █████╗ ███████╗ ██║ ██║ ██╔██╗ ██║ ██║ ███╗ ██║ ██║ ██║ ██║ ██║ ███████╗
██║╚██╔╝██║ ██║ ██║╚██╗██║ ██╔══██║ ██║ ██╔══╝ ╚════██║ ██║ ██║ ██║╚██╗██║ ██║ ██║ ██║ ██║ ██║ ██║ ██║ ╚════██║
██║ ╚═╝ ██║ ██║ ██║ ╚████║ ██║ ██║ ██║ ███████╗ ███████║ ██║ ██║ ██║ ╚████║ ╚██████╔╝ ╚██████╔╝ ██║ ██║ ███████╗ ███████║
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝





Please load the Mina REPL context by executing .loadMina before running any commands.



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 { priv: zkAppPrivateKey, pub: zkAppAddress } = mina.genKeyPair();

mina-testing-utils> let zkApp = new Add(zkAppAddress);

mina-testing-utils> let { privateKey: deployerKey, publicKey: deployerAccount } = mina.testAccounts[0];

mina-testing-utils> let txn = await mina.snarkyjs.Mina.transaction(deployerAccount, () => {
mina.snarkyjs.AccountUpdate.fundNewAccount(deployerAccount);
zkApp.deploy();
});

mina-testing-utils> await txn.prove();
mina-testing-utils> await txn.sign([deployerKey, zkAppPrivateKey]).send();

mina-testing-utils> console.log("Stored number in state is: ", zkApp.num.get().toString())
Stored number in state is: 1
```
See it in action:
[![asciicast](https://asciinema.org/a/603288.svg)](https://asciinema.org/a/603288)
# Development
## Requirements
Expand Down
36 changes: 36 additions & 0 deletions cli/cli.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node --experimental-vm-modules --experimental-wasm-threads

const chalk = require("chalk");
const { printBanner } = require("./gui.cjs");
const configureRepl = require("./repl.cjs");

printBanner();

/**
* 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();

return {
priv,
pub,
};
};
}

/**
* 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);
}

// Start a REPL session with the given options.
configureRepl(dynamicImport, genKeyPair);
56 changes: 56 additions & 0 deletions cli/gui.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @fileoverview This module exports functions to print banners and messages related to Mina Testing Utils.
* @module cli/gui
* @requires cfonts
* @requires chalk
*/

const cfonts = require("cfonts");
const chalk = require("chalk");

const loadMinaMsg = `
Please load the Mina REPL context by executing ${chalk.underline.bold(
".loadMina"
)} before running any commands.
`;

/**
* Prints a banner with the name of the module.
* @function
* @name printBanner
* @returns {void}
*/
module.exports.printBanner = function printBanner() {
cfonts.say("Mina Testing Utils", {
font: "block",
align: "left",
gradient: ["red", "yellow"],
});
console.log(
`
${chalk.bold(loadMinaMsg)}
`
);
};

/**
* Prints an error message indicating that the Mina REPL context needs to be loaded.
* @function
* @name printLoadMinaErrorMsg
* @returns {void}
*/
module.exports.printLoadMinaErrorMsg = function printLoadMinaErrorMsg() {
console.log(chalk.red(loadMinaMsg));
};

/**
* Returns a success message indicating that the Mina REPL context was loaded successfully.
* @function
* @name minaLoadedOkMsg
* @returns {string} The success message.
*/
module.exports.minaLoadedOkMsg = function minaLoadedOkMsg() {
return `Snarky loaded successfully! You can access it through the ${chalk.green.bold("mina")} object.`;
};
88 changes: 88 additions & 0 deletions cli/repl.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const repl = require("repl");
const ora = require("ora");
const homedir = require("os").homedir();

const { printLoadMinaErrorMsg, minaLoadedOkMsg } = require("./gui.cjs");

function configureRepl(dynamicImport, genKeyPair) {
// Start a REPL session with the given options.
const REPL = repl.start({
prompt: "mina-testing-utils> ",
ignoreUndefined: true,
replMode: repl.REPL_MODE_STRICT,
require,
});

// Set command history file.
REPL.setupHistory(`${homedir}/.mina-testing-utils-history`, (err) => {
if (err) console.warn("Repl history could not be loaded.");
});

// Set the await flag to true in the REPL context.
REPL.context.await = true;
// Add extra context to the REPL context.
const extraContext = {
mina: {
loadContractFromPath: dynamicImport,
},
};
Object.assign(REPL.context, extraContext);

// Override the eval function to print a message if the user tries to run a command before loading the Mina context.
const evalFn = REPL.eval;
REPL.eval = (cmd, context, filename, callback) => {
printLoadMinaErrorMsg();
evalFn(cmd, context, filename, (err, result) => {
if (err) {
callback(err);
} else {
if (result instanceof Promise) {
result.then((res) => {
callback(null, res);
});
} else {
callback(null, result);
}
}
});
};
// Define a custom command for the REPL.
REPL.defineCommand("loadMina", {
help: "Loads snarkyjs",
action() {
const spinner = ora({
text: "Loading snarkyjs...",
discardStdin: false,
}).start();
this.clearBufferedCommand();
dynamicImport("snarkyjs")
.then((snarkyjs) => {
const local = snarkyjs.Mina.LocalBlockchain({
proofsEnabled: false,
});
snarkyjs.Mina.setActiveInstance(local);

const minaContext = {
snarkyjs,
local,
testAccounts: local.testAccounts,
genKeyPair: genKeyPair(snarkyjs),
};

Object.assign(this.context.mina, minaContext);

spinner.succeed(minaLoadedOkMsg());
console.log();
// Restore eval function.
this.displayPrompt();
REPL.eval = evalFn;
})
.catch((err) => {
console.log(err);
spinner.fail("Error while loading snarkyjs");
});
},
});
}

module.exports = configureRepl;
Loading

0 comments on commit 756a5d0

Please sign in to comment.