From dc98b832805c1401aadf72b6d593fda1524d7209 Mon Sep 17 00:00:00 2001 From: Roshan <19766713+rpalakkal@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:30:29 -0500 Subject: [PATCH] v0.1.4 (#7) * feat: move caller to Query.send from prankFulfill * feat: capture stdout and stderr from ffi commands * chore: add caller with send in a test * feat: polish stdout and stderr handing * feat: make Axiom library functions public to trigger a call and enable expectEmit in AxiomTest --------- Co-authored-by: Yi Sun --- build/axiom-std-cli-build.js | 137 ++++++++++++++++++++++------------ cli/compile.ts | 16 +++- cli/prove.ts | 15 +++- cli/utils.ts | 28 +++++++ src/AxiomCli.sol | 2 +- src/AxiomTest.sol | 3 +- src/AxiomVm.sol | 69 ++++++++++++----- test/AverageBalance.t.sol | 10 ++- test/circuit/array.circuit.ts | 7 +- 9 files changed, 207 insertions(+), 80 deletions(-) diff --git a/build/axiom-std-cli-build.js b/build/axiom-std-cli-build.js index 5da2c00..1d0f4b8 100644 --- a/build/axiom-std-cli-build.js +++ b/build/axiom-std-cli-build.js @@ -4,60 +4,16 @@ var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; -// dist/compile.js -var require_compile = __commonJS({ - "dist/compile.js"(exports2) { - "use strict"; - Object.defineProperty(exports2, "__esModule", { value: true }); - exports2.compile = void 0; - var js_12 = require("@axiom-crypto/circuit/js"); - var utils_12 = require("@axiom-crypto/circuit/cliHandler/utils"); - var compile = async (circuitPath, providerUri2, options) => { - let circuitFunction = "circuit"; - const f = await (0, utils_12.getFunctionFromTs)(circuitPath, circuitFunction); - const provider2 = (0, utils_12.getProvider)(providerUri2); - const circuit2 = new js_12.AxiomBaseCircuit({ - f: f.circuit, - mock: true, - provider: provider2, - shouldTime: false, - inputSchema: f.inputSchema - }); - try { - const res = await circuit2.mockCompile(f.defaultInputs); - if (options.overrideQuerySchema) { - if (!/^[A-F0-9]+$/i.test(options.overrideQuerySchema)) { - throw new Error("overrideQuerySchema is not a hex string"); - } - res.querySchema = ("0xdeadbeef" + options.overrideQuerySchema).padEnd(66, "0").substring(0, 66); - } - const circuitFn = `const ${f.importName} = AXIOM_CLIENT_IMPORT -${f.circuit.toString()}`; - const encoder = new TextEncoder(); - const circuitBuild = encoder.encode(circuitFn); - const build = { - ...res, - circuit: Buffer.from(circuitBuild).toString("base64") - }; - console.log(JSON.stringify(build)); - } catch (e) { - console.error(e); - } - }; - exports2.compile = compile; - } -}); - // dist/utils.js var require_utils = __commonJS({ "dist/utils.js"(exports2) { "use strict"; Object.defineProperty(exports2, "__esModule", { value: true }); - exports2.getInputs = exports2.getAbis = exports2.findStructDefinition = exports2.findFilesWithAxiomInput = void 0; + exports2.redirectConsole = exports2.getInputs = exports2.getAbis = exports2.findStructDefinition = exports2.findFilesWithAxiomInput = void 0; var tslib_1 = require("tslib"); var fs_1 = tslib_1.__importDefault(require("fs")); var path_1 = tslib_1.__importDefault(require("path")); - var viem_1 = require("viem"); + var viem_12 = require("viem"); var findFilesWithAxiomInput = (directory) => { let files = []; function traverseDirectory(dir) { @@ -157,7 +113,7 @@ var require_utils = __commonJS({ throw new Error("Could not find valid ABI"); } const abi = abis[0]; - const rawInputs = (0, viem_1.decodeAbiParameters)(abi, inputs2)[0]; + const rawInputs = (0, viem_12.decodeAbiParameters)(abi, inputs2)[0]; const abiComponents = abi[0].components; const circuitInputs2 = {}; for (let i = 0; i < keys.length; i++) { @@ -170,6 +126,82 @@ var require_utils = __commonJS({ return circuitInputs2; }; exports2.getInputs = getInputs; + var redirectConsole = () => { + const originalConsoleLog = console.log; + const originalConsoleError = console.error; + let logString = ""; + let errorString = ""; + console.log = (...args) => { + logString += args.join(" ") + "\n "; + }; + console.error = (...args) => { + errorString += args.join(" ") + "\n "; + }; + const restoreConsole2 = () => { + console.log = originalConsoleLog; + console.error = originalConsoleError; + }; + const getCaptures2 = () => ({ + logs: logString, + errors: errorString + }); + return { restoreConsole: restoreConsole2, getCaptures: getCaptures2 }; + }; + exports2.redirectConsole = redirectConsole; + } +}); + +// dist/compile.js +var require_compile = __commonJS({ + "dist/compile.js"(exports2) { + "use strict"; + Object.defineProperty(exports2, "__esModule", { value: true }); + exports2.compile = void 0; + var js_12 = require("@axiom-crypto/circuit/js"); + var utils_12 = require("@axiom-crypto/circuit/cliHandler/utils"); + var utils_22 = require_utils(); + var viem_12 = require("viem"); + var compile = async (circuitPath, providerUri2, options) => { + const { restoreConsole: restoreConsole2, getCaptures: getCaptures2 } = (0, utils_22.redirectConsole)(); + let circuitFunction = "circuit"; + const f = await (0, utils_12.getFunctionFromTs)(circuitPath, circuitFunction); + const provider2 = (0, utils_12.getProvider)(providerUri2); + const circuit2 = new js_12.AxiomBaseCircuit({ + f: f.circuit, + mock: true, + provider: provider2, + shouldTime: false, + inputSchema: f.inputSchema + }); + try { + const res = await circuit2.mockCompile(f.defaultInputs); + if (options.overrideQuerySchema) { + if (!/^[A-F0-9]+$/i.test(options.overrideQuerySchema)) { + throw new Error("overrideQuerySchema is not a hex string"); + } + res.querySchema = ("0xdeadbeef" + options.overrideQuerySchema).padEnd(66, "0").substring(0, 66); + } + const circuitFn = `const ${f.importName} = AXIOM_CLIENT_IMPORT +${f.circuit.toString()}`; + const encoder = new TextEncoder(); + const circuitBuild = encoder.encode(circuitFn); + const build = { + ...res, + circuit: Buffer.from(circuitBuild).toString("base64") + }; + const logs = getCaptures2(); + const output = (0, viem_12.encodeAbiParameters)((0, viem_12.parseAbiParameters)("string x, string y, string z"), [logs.logs, logs.errors, JSON.stringify(build)]); + restoreConsole2(); + console.log(output); + } catch (e) { + console.error(e); + const logs = getCaptures2(); + const output = (0, viem_12.encodeAbiParameters)((0, viem_12.parseAbiParameters)("string x, string y, string z"), [logs.logs, logs.errors, ""]); + restoreConsole2(); + console.log(output); + } + }; + exports2.compile = compile; } }); @@ -182,10 +214,12 @@ var require_prove = __commonJS({ var js_1 = require("@axiom-crypto/circuit/js"); var utils_1 = require("@axiom-crypto/circuit/cliHandler/utils"); var utils_2 = require_utils(); + var viem_1 = require("viem"); var core_1 = require("@axiom-crypto/core"); var client_1 = require("@axiom-crypto/client"); var utils_3 = require("@axiom-crypto/client/axiom/utils"); var prove = async (compiledJson, inputs, providerUri, sourceChainId, callbackTarget, callbackExtraData, refundAddress, maxFeePerGas, callbackGasLimit, caller) => { + const { restoreConsole, getCaptures } = (0, utils_2.redirectConsole)(); const decoder = new TextDecoder(); const provider = (0, utils_1.getProvider)(providerUri); let compiled = JSON.parse(compiledJson); @@ -242,9 +276,16 @@ var require_prove = __commonJS({ calldata: build.calldata, computeResults }; - console.log(JSON.stringify(query)); + const logs = getCaptures(); + const output = (0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)("string x, string y, string z"), [logs.logs, logs.errors, JSON.stringify(query)]); + restoreConsole(); + console.log(output); } catch (e) { console.error(e); + const logs = getCaptures(); + const output = (0, viem_1.encodeAbiParameters)((0, viem_1.parseAbiParameters)("string x, string y, string z"), [logs.logs, logs.errors, ""]); + restoreConsole(); + console.log(output); } }; exports.prove = prove; diff --git a/cli/compile.ts b/cli/compile.ts index ea477d8..185a9f7 100644 --- a/cli/compile.ts +++ b/cli/compile.ts @@ -1,11 +1,14 @@ import { AxiomBaseCircuit } from "@axiom-crypto/circuit/js"; import { getFunctionFromTs, getProvider } from "@axiom-crypto/circuit/cliHandler/utils"; +import { redirectConsole } from "./utils"; +import { encodeAbiParameters, parseAbiParameters } from "viem"; export const compile = async ( circuitPath: string, providerUri: string, - options: {overrideQuerySchema?: string } + options: { overrideQuerySchema?: string } ) => { + const { restoreConsole, getCaptures } = redirectConsole(); let circuitFunction = "circuit"; const f = await getFunctionFromTs(circuitPath, circuitFunction); const provider = getProvider(providerUri); @@ -24,7 +27,7 @@ export const compile = async ( throw new Error("overrideQuerySchema is not a hex string"); } res.querySchema = ("0xdeadbeef" + options.overrideQuerySchema).padEnd(66, '0').substring(0, 66); - } + } const circuitFn = `const ${f.importName} = AXIOM_CLIENT_IMPORT\n${f.circuit.toString()}`; const encoder = new TextEncoder(); const circuitBuild = encoder.encode(circuitFn); @@ -32,9 +35,16 @@ export const compile = async ( ...res, circuit: Buffer.from(circuitBuild).toString('base64'), } - console.log(JSON.stringify(build)); + const logs = getCaptures(); + const output = encodeAbiParameters(parseAbiParameters('string x, string y, string z'), [logs.logs, logs.errors, JSON.stringify(build)]) + restoreConsole(); + console.log(output); } catch (e) { console.error(e); + const logs = getCaptures(); + const output = encodeAbiParameters(parseAbiParameters('string x, string y, string z'), [logs.logs, logs.errors, ""]) + restoreConsole(); + console.log(output); } } \ No newline at end of file diff --git a/cli/prove.ts b/cli/prove.ts index ec15d5b..28cc088 100644 --- a/cli/prove.ts +++ b/cli/prove.ts @@ -1,8 +1,8 @@ import path from 'path'; import { AxiomBaseCircuit } from "@axiom-crypto/circuit/js"; import { getFunctionFromTs, getProvider, readInputs, saveJsonToFile } from "@axiom-crypto/circuit/cliHandler/utils"; -import { getInputs } from './utils'; -import { decodeAbiParameters } from 'viem'; +import { getInputs, redirectConsole } from './utils'; +import { decodeAbiParameters, encodeAbiParameters, parseAbiParameters } from 'viem'; import { AxiomSdkCore } from "@axiom-crypto/core"; import { buildSendQuery } from "@axiom-crypto/client"; import { argsArrToObj } from '@axiom-crypto/client/axiom/utils'; @@ -19,7 +19,7 @@ export const prove = async ( callbackGasLimit: string, caller: string, ) => { - + const { restoreConsole, getCaptures } = redirectConsole(); const decoder = new TextDecoder(); const provider = getProvider(providerUri); @@ -86,9 +86,16 @@ export const prove = async ( computeResults, }; - console.log(JSON.stringify(query)); + const logs = getCaptures(); + const output = encodeAbiParameters(parseAbiParameters('string x, string y, string z'), [logs.logs, logs.errors, JSON.stringify(query)]) + restoreConsole(); + console.log(output); } catch (e) { console.error(e); + const logs = getCaptures(); + const output = encodeAbiParameters(parseAbiParameters('string x, string y, string z'), [logs.logs, logs.errors, ""]) + restoreConsole(); + console.log(output); } } \ No newline at end of file diff --git a/cli/utils.ts b/cli/utils.ts index 5ddb598..2435267 100644 --- a/cli/utils.ts +++ b/cli/utils.ts @@ -133,4 +133,32 @@ export const getInputs = (inputs: string, inputSchema: string): any => { } } return circuitInputs; +} + +export const redirectConsole = () => { + const originalConsoleLog = console.log; + const originalConsoleError = console.error; + + let logString = ''; + let errorString = ''; + + console.log = (...args) => { + logString += args.join(' ') + '\n '; + }; + + console.error = (...args) => { + errorString += args.join(' ') + '\n '; + }; + + const restoreConsole = () => { + console.log = originalConsoleLog; + console.error = originalConsoleError; + }; + + const getCaptures = () => ({ + logs: logString, + errors: errorString + }); + + return { restoreConsole, getCaptures }; } \ No newline at end of file diff --git a/src/AxiomCli.sol b/src/AxiomCli.sol index 2715a89..69210d6 100644 --- a/src/AxiomCli.sol +++ b/src/AxiomCli.sol @@ -4,5 +4,5 @@ pragma solidity ^0.8.0; library AxiomCli { /// @dev The SHA256 hash of the Axiom CLI binary - bytes public constant CLI_SHASUM = hex"9b2cdc50d6d5b9501411debd8e390f9405b032019006ecf5ee24bf751768f80e"; + bytes public constant CLI_SHASUM = hex"f0f355f27f363bab6acb092fabeeffcf63b80ba08a7bd13577317a6732a90ff7"; } \ No newline at end of file diff --git a/src/AxiomTest.sol b/src/AxiomTest.sol index 65973e2..4057ff7 100644 --- a/src/AxiomTest.sol +++ b/src/AxiomTest.sol @@ -131,7 +131,8 @@ abstract contract AxiomTest is Test { callbackExtraData: callbackExtraData, feeData: feeData, axiomVm: axiomVm, - outputString: "" + outputString: "", + caller: msg.sender }); } } diff --git a/src/AxiomVm.sol b/src/AxiomVm.sol index 0583672..15561b2 100644 --- a/src/AxiomVm.sol +++ b/src/AxiomVm.sol @@ -60,6 +60,7 @@ struct FulfillCallbackArgs { /// @param feeData The fee data for the query /// @param axiomVm The AxiomVm contract /// @param outputString The output string from the query +/// @param caller The address of the caller of the original query into Axiom struct Query { bytes32 querySchema; bytes input; @@ -68,6 +69,7 @@ struct Query { IAxiomV2Query.AxiomV2FeeData feeData; AxiomVm axiomVm; string outputString; + address caller; } /// @title Axiom @@ -75,26 +77,28 @@ struct Query { library Axiom { /// @dev Sends a query to Axiom /// @param self The query to send - function send(Query memory self) internal { + function send(Query memory self) public { self.outputString = self.axiomVm.getArgsAndSendQuery( self.querySchema, self.input, self.callbackTarget, self.callbackExtraData, self.feeData ); } - /// @dev Pranks a callback from Axiom - /// @param self The query to fulfill the callback for - /// @return results The results of the query - function prankFulfill(Query memory self) internal returns (bytes32[] memory results) { - results = prankFulfill(self, msg.sender); + /// @dev Sends a query to Axiom + /// @param self The query to send + /// @param caller The address of the caller of the original query into Axiom + function send(Query memory self, address caller) public { + self.outputString = self.axiomVm.getArgsAndSendQuery( + self.querySchema, self.input, self.callbackTarget, self.callbackExtraData, self.feeData + ); + self.caller = caller; } /// @dev Pranks a callback from Axiom /// @param self The query to fulfill the callback for - /// @param caller The address of the caller of the original query into Axiom /// @return results The results of the query - function prankFulfill(Query memory self, address caller) internal returns (bytes32[] memory results) { + function prankFulfill(Query memory self) public returns (bytes32[] memory results) { FulfillCallbackArgs memory args = self.axiomVm.fulfillCallbackArgs( - self.querySchema, self.input, self.callbackTarget, self.callbackExtraData, self.feeData, caller + self.querySchema, self.input, self.callbackTarget, self.callbackExtraData, self.feeData, self.caller ); self.axiomVm.prankCallback(args); results = args.axiomResults; @@ -171,6 +175,28 @@ contract AxiomVm is Test { ); } + /** + * @dev Logs FFI logs and reverts if stderr is not empty + * @param phase a string indicating the phase of circuit processing, one of `Compile` or `Prove` + * @param logs any logs from ffi to log + * @param errors any errors from ffi to log + * @param message the revert message + */ + function logOutput(string memory phase, string memory logs, string memory errors, string memory message) + public + view + { + if (bytes(logs).length > 0) { + console.log(string.concat(phase, " - Circuit stdout:")); + console.log(logs); + } + if (bytes(errors).length > 0) { + console.log(string.concat(phase, " - Circuit stderr:")); + console.log(errors); + revert(message); + } + } + /** * @dev Compiles a circuit using the Axiom CLI via FFI * @param _circuitPath path to the circuit file @@ -184,10 +210,11 @@ contract AxiomVm is Test { cli[3] = _circuitPath; cli[4] = vm.rpcUrl(urlOrAlias); bytes memory axiomOutput = vm.ffi(cli); - - string memory artifact = string(axiomOutput); - querySchema = bytes32(vm.parseJson(artifact, ".querySchema")); - compiledStrings[querySchema] = artifact; + (string memory logs, string memory errors, string memory build) = + abi.decode(axiomOutput, (string, string, string)); + logOutput("Compile", logs, errors, "Circuit compilation failed"); + querySchema = bytes32(vm.parseJson(build, ".querySchema")); + compiledStrings[querySchema] = build; } /** @@ -206,11 +233,12 @@ contract AxiomVm is Test { cli[5] = "--override-query-schema"; cli[6] = suffix; bytes memory axiomOutput = vm.ffi(cli); - - string memory artifact = string(axiomOutput); - querySchema = bytes32(vm.parseJson(artifact, ".querySchema")); - compiledStrings[querySchema] = artifact; - } + (string memory logs, string memory errors, string memory build) = + abi.decode(axiomOutput, (string, string, string)); + logOutput("Compile", logs, errors, "Circuit compilation failed"); + querySchema = bytes32(vm.parseJson(build, ".querySchema")); + compiledStrings[querySchema] = build; + } /** * @dev Generates args for the sendQuery function @@ -459,7 +487,10 @@ contract AxiomVm is Test { cli[12] = vm.toString(msg.sender); bytes memory axiomOutput = vm.ffi(cli); - output = string(axiomOutput); + (string memory logs, string memory errors, string memory build) = + abi.decode(axiomOutput, (string, string, string)); + logOutput("Prove", logs, errors, "Circuit proving failed"); + output = build; } /** diff --git a/test/AverageBalance.t.sol b/test/AverageBalance.t.sol index e96b597..798f273 100644 --- a/test/AverageBalance.t.sol +++ b/test/AverageBalance.t.sol @@ -9,6 +9,8 @@ import { AverageBalance } from "./example/AverageBalance.sol"; contract AverageBalanceTest is AxiomTest { using Axiom for Query; + event AverageBalanceStored(uint256 blockNumber, address _address, uint256 averageBalance); + struct AxiomInput { uint64 blockNumber; address _address; @@ -24,7 +26,6 @@ contract AverageBalanceTest is AxiomTest { input = AxiomInput({ blockNumber: 4_205_938, _address: address(0x8018fe32FCFd3d166e8B4c4e37105318a84ba11d) }); querySchema = axiomVm.readCircuit("test/circuit/average.circuit.ts", "aaaa"); - console.logBytes32(querySchema); averageBalance = new AverageBalance(axiomV2QueryAddress, uint64(block.chainid), querySchema); } @@ -37,6 +38,8 @@ contract AverageBalanceTest is AxiomTest { q.send(); // prank fulfillment of the query, returning the Axiom results + vm.expectEmit(); + emit AverageBalanceStored(input.blockNumber, input._address, 0); bytes32[] memory results = q.prankFulfill(); // parse Axiom results and verify length is as expected @@ -63,9 +66,12 @@ contract AverageBalanceTest is AxiomTest { Query memory q = query(querySchema, abi.encode(input), address(averageBalance), callbackExtraData, feeData); // send the query to Axiom - q.send(); + address caller = address(123); + q.send(caller); // prank fulfillment of the query, returning the Axiom results + vm.expectEmit(); + emit AverageBalanceStored(input.blockNumber, input._address, 0); bytes32[] memory results = q.prankFulfill(); // parse Axiom results and verify length is as expected diff --git a/test/circuit/array.circuit.ts b/test/circuit/array.circuit.ts index 67a6cfd..edfed0b 100644 --- a/test/circuit/array.circuit.ts +++ b/test/circuit/array.circuit.ts @@ -10,7 +10,8 @@ import { constant, witness, getAccount, - sum + sum, + log } from "@axiom-crypto/client"; export interface CircuitInputs { @@ -27,6 +28,8 @@ export const defaultInputs = { } export const circuit = async (inputs: CircuitInputs) => { - sum(inputs.blockNumbers); + let res = sum(inputs.blockNumbers); + console.log("Sum of inputs.blockNumbers: ", res.value()); + console.log("Input address: ", inputs.address.value().toString(16)); addToCallback(inputs.address); }; \ No newline at end of file