From 6b68e00f5c0fa810d4779db85e9d7c18a8e17265 Mon Sep 17 00:00:00 2001 From: Hugo C <911307+hugocaillard@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:30:26 -0500 Subject: [PATCH] feat: add costs report in sdk tx response (#1620) * feat: add costs report in sdk tx response * chore: update dependencies * chore: update dependencies --- components/clarinet-sdk-wasm/src/core.rs | 12 ++--- .../clarinet-sdk-wasm/src/utils/costs.rs | 44 ------------------- components/clarinet-sdk-wasm/src/utils/mod.rs | 1 - .../clarinet-sdk/browser/src/sdkProxy.ts | 2 + .../common/src/sdkProxyHelpers.ts | 42 ++++++++++++++++++ components/clarinet-sdk/node/src/sdkProxy.ts | 2 + .../node/tests/simnet-usage.test.ts | 29 +++++++++++- components/clarity-repl/src/repl/session.rs | 2 +- 8 files changed, 79 insertions(+), 55 deletions(-) delete mode 100644 components/clarinet-sdk-wasm/src/utils/costs.rs diff --git a/components/clarinet-sdk-wasm/src/core.rs b/components/clarinet-sdk-wasm/src/core.rs index 07a48821c..66868e91f 100644 --- a/components/clarinet-sdk-wasm/src/core.rs +++ b/components/clarinet-sdk-wasm/src/core.rs @@ -35,7 +35,6 @@ use std::{panic, path::PathBuf}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; -use crate::utils::costs::SerializableCostsReport; use crate::utils::events::serialize_event; #[wasm_bindgen] @@ -217,6 +216,7 @@ pub struct TxArgs { pub struct TransactionRes { pub result: String, pub events: String, + pub costs: String, } #[derive(Serialize, Deserialize)] @@ -248,6 +248,7 @@ pub fn execution_result_to_transaction_res(execution: &ExecutionResult) -> Trans TransactionRes { result, events: json!(events_as_strings).to_string(), + costs: json!(execution.cost).to_string(), } } @@ -1076,13 +1077,8 @@ impl SDK { let coverage = session.collect_lcov_content(&asts, &contract_paths); - let mut costs_reports = Vec::new(); - costs_reports.append(&mut self.costs_reports); - let costs_reports: Vec = costs_reports - .iter() - .map(SerializableCostsReport::from_vm_costs_report) - .collect(); - let costs = serde_json::to_string(&costs_reports).map_err(|e| e.to_string())?; + let costs = serde_json::to_string(&self.costs_reports).map_err(|e| e.to_string())?; + self.costs_reports.clear(); Ok(SessionReport { coverage, costs }) } diff --git a/components/clarinet-sdk-wasm/src/utils/costs.rs b/components/clarinet-sdk-wasm/src/utils/costs.rs deleted file mode 100644 index b0c0798a9..000000000 --- a/components/clarinet-sdk-wasm/src/utils/costs.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - Implements a custom CostReport struct because the clarity-vm CostSynthesis struct - does not have the serde::Serialize macro. This is a fix to avoid modifying the VM code. - Let's try to update it in the upcoming vm version (with epoch 3) -*/ - -use clarity_repl::{clarity::costs::ExecutionCost, repl::session::CostsReport}; -use serde::Serialize; - -#[derive(Clone, Debug, Serialize)] -pub struct CostSynthesis { - pub total: ExecutionCost, - pub limit: ExecutionCost, - pub memory: u64, - pub memory_limit: u64, -} - -#[derive(Clone, Debug, Serialize)] -pub struct SerializableCostsReport { - pub test_name: String, - pub contract_id: String, - pub method: String, - pub args: Vec, - pub cost_result: CostSynthesis, -} - -impl SerializableCostsReport { - pub fn from_vm_costs_report(costs_report: &CostsReport) -> Self { - let cost_result = CostSynthesis { - total: costs_report.cost_result.total.clone(), - limit: costs_report.cost_result.limit.clone(), - memory: costs_report.cost_result.memory, - memory_limit: costs_report.cost_result.memory_limit, - }; - - SerializableCostsReport { - test_name: costs_report.test_name.clone(), - contract_id: costs_report.contract_id.clone(), - method: costs_report.method.clone(), - args: costs_report.args.clone(), - cost_result, - } - } -} diff --git a/components/clarinet-sdk-wasm/src/utils/mod.rs b/components/clarinet-sdk-wasm/src/utils/mod.rs index 539658aaf..a9970c28f 100644 --- a/components/clarinet-sdk-wasm/src/utils/mod.rs +++ b/components/clarinet-sdk-wasm/src/utils/mod.rs @@ -1,2 +1 @@ -pub mod costs; pub mod events; diff --git a/components/clarinet-sdk/browser/src/sdkProxy.ts b/components/clarinet-sdk/browser/src/sdkProxy.ts index 67bb68b05..8cce4149b 100644 --- a/components/clarinet-sdk/browser/src/sdkProxy.ts +++ b/components/clarinet-sdk/browser/src/sdkProxy.ts @@ -18,6 +18,7 @@ import { type ParsedTransactionResult, type Execute, type TransferSTX, + parseCosts, } from "../../common/src/sdkProxyHelpers.js"; /** @deprecated use `simnet.execute(command)` instead */ @@ -48,6 +49,7 @@ function parseTxResponse(response: TransactionRes): ParsedTransactionResult { return { result: Cl.deserialize(response.result), events: parseEvents(response.events), + costs: parseCosts(response.costs), }; } diff --git a/components/clarinet-sdk/common/src/sdkProxyHelpers.ts b/components/clarinet-sdk/common/src/sdkProxyHelpers.ts index 022edabb9..10b918944 100644 --- a/components/clarinet-sdk/common/src/sdkProxyHelpers.ts +++ b/components/clarinet-sdk/common/src/sdkProxyHelpers.ts @@ -5,9 +5,25 @@ export type ClarityEvent = { data: { raw_value?: string; value?: ClarityValue; [key: string]: any }; }; +export type ExecutionCost = { + writeLength: number; + writeCount: number; + readLength: number; + readCount: number; + runtime: number; +}; + +export type ClarityCosts = { + total: ExecutionCost; + limit: ExecutionCost; + memory: number; + memory_limit: number; +}; + export type ParsedTransactionResult = { result: ClarityValue; events: ClarityEvent[]; + costs: ClarityCosts | null; }; export type CallFn = ( @@ -113,6 +129,32 @@ export function parseEvents(events: string): ClarityEvent[] { } } +export function parseCosts(costs: string): ClarityCosts | null { + try { + let { memory, memory_limit, total, limit } = JSON.parse(costs); + return { + memory: memory, + memory_limit: memory_limit, + total: { + writeLength: total.write_length, + writeCount: total.write_count, + readLength: total.read_length, + readCount: total.read_count, + runtime: total.runtime, + }, + limit: { + writeLength: limit.write_length, + writeCount: limit.write_count, + readLength: limit.read_length, + readCount: limit.read_count, + runtime: limit.runtime, + }, + }; + } catch (_e) { + return null; + } +} + export type MineBlock = (txs: Array) => ParsedTransactionResult[]; export type Execute = (snippet: string) => ParsedTransactionResult; export type GetDataVar = (contract: string, dataVar: string) => ClarityValue; diff --git a/components/clarinet-sdk/node/src/sdkProxy.ts b/components/clarinet-sdk/node/src/sdkProxy.ts index 32198eff7..8a8c2326d 100644 --- a/components/clarinet-sdk/node/src/sdkProxy.ts +++ b/components/clarinet-sdk/node/src/sdkProxy.ts @@ -18,6 +18,7 @@ import { type ParsedTransactionResult, type Execute, type TransferSTX, + parseCosts, } from "../../common/src/sdkProxyHelpers.js"; /** @deprecated use `simnet.execute(command)` instead */ @@ -48,6 +49,7 @@ function parseTxResponse(response: TransactionRes): ParsedTransactionResult { return { result: Cl.deserialize(response.result), events: parseEvents(response.events), + costs: parseCosts(response.costs), }; } diff --git a/components/clarinet-sdk/node/tests/simnet-usage.test.ts b/components/clarinet-sdk/node/tests/simnet-usage.test.ts index 258866fa3..4ccb8e5d7 100644 --- a/components/clarinet-sdk/node/tests/simnet-usage.test.ts +++ b/components/clarinet-sdk/node/tests/simnet-usage.test.ts @@ -28,7 +28,10 @@ function deleteExistingDeploymentPlan() { beforeEach(async () => { deleteExistingDeploymentPlan(); - simnet = await initSimnet("tests/fixtures/Clarinet.toml"); + simnet = await initSimnet("tests/fixtures/Clarinet.toml", false, { + trackCosts: true, + trackCoverage: false, + }); }); afterEach(() => { @@ -146,6 +149,30 @@ describe("simnet can call contracts function", () => { expect(printEvent.data.value).toStrictEqual(Cl.stringAscii("call increment")); }); + it("reports costs", () => { + const res = simnet.callPublicFn("counter", "increment", [], address1); + + expect(res).toHaveProperty("costs"); + expect(res.costs).toStrictEqual({ + memory: 417, + memory_limit: 100000000, + total: { + writeLength: 44, + writeCount: 3, + readLength: 1466, + readCount: 8, + runtime: 15630, + }, + limit: { + writeLength: 15000000, + writeCount: 15000, + readLength: 100000000, + readCount: 15000, + runtime: 5000000000, + }, + }); + }); + it("can call public functions with arguments", () => { const res = simnet.callPublicFn("counter", "add", [Cl.uint(2)], address1); diff --git a/components/clarity-repl/src/repl/session.rs b/components/clarity-repl/src/repl/session.rs index dd672ccbe..6f82d7f5d 100644 --- a/components/clarity-repl/src/repl/session.rs +++ b/components/clarity-repl/src/repl/session.rs @@ -84,7 +84,7 @@ lazy_static! { }; } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] pub struct CostsReport { pub test_name: String, pub contract_id: String,