From ee8cc7acb42316c58d5f79f021fcc5c5118e01ef Mon Sep 17 00:00:00 2001 From: Hugo CAILLARD <911307+hugocaillard@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:12:42 +0100 Subject: [PATCH] feat: add costs rport in sdk tx response --- Cargo.lock | 17 ++++--- Cargo.toml | 5 +++ 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 +- 10 files changed, 95 insertions(+), 61 deletions(-) delete mode 100644 components/clarinet-sdk-wasm/src/utils/costs.rs diff --git a/Cargo.lock b/Cargo.lock index 7d08a2cd0..255ed990a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -800,7 +800,6 @@ dependencies = [ [[package]] name = "clarity" version = "2.3.0" -source = "git+https://github.com/stacks-network/stacks-core.git?branch=feat/clarity-wasm-develop#28c1d9eecfd26443b6f78de754b51b9dcfac8402" dependencies = [ "getrandom 0.2.8", "hashbrown 0.14.3", @@ -873,7 +872,7 @@ dependencies = [ "log", "memchr", "pico-args", - "pox-locking", + "pox-locking 2.4.0 (git+https://github.com/stacks-network/stacks-core.git?branch=feat/clarity-wasm-develop)", "prettytable-rs", "regex", "reqwest", @@ -2644,7 +2643,6 @@ dependencies = [ [[package]] name = "libstackerdb" version = "0.0.1" -source = "git+https://github.com/stacks-network/stacks-core.git?branch=feat/clarity-wasm-develop#28c1d9eecfd26443b6f78de754b51b9dcfac8402" dependencies = [ "clarity", "secp256k1 0.24.3", @@ -3311,6 +3309,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "pox-locking" +version = "2.4.0" +dependencies = [ + "clarity", + "slog", + "stacks-common", +] + [[package]] name = "pox-locking" version = "2.4.0" @@ -4585,7 +4592,6 @@ dependencies = [ [[package]] name = "stacks-common" version = "0.0.2" -source = "git+https://github.com/stacks-network/stacks-core.git?branch=feat/clarity-wasm-develop#28c1d9eecfd26443b6f78de754b51b9dcfac8402" dependencies = [ "chrono", "curve25519-dalek 2.0.0", @@ -4684,7 +4690,6 @@ dependencies = [ [[package]] name = "stackslib" version = "0.0.1" -source = "git+https://github.com/stacks-network/stacks-core.git?branch=feat/clarity-wasm-develop#28c1d9eecfd26443b6f78de754b51b9dcfac8402" dependencies = [ "chrono", "clar2wasm", @@ -4699,7 +4704,7 @@ dependencies = [ "mio 0.6.23", "nix 0.23.2", "percent-encoding", - "pox-locking", + "pox-locking 2.4.0", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", diff --git a/Cargo.toml b/Cargo.toml index 2b9bac359..14e884f43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,8 @@ web-sys = { version = "0.3" } chainhook-sdk = { git = "https://github.com/hirosystems/chainhook.git" } chainhook-types = { git = "https://github.com/hirosystems/chainhook.git" } stacks-codec = { path = "./components/stacks-codec" } + +[patch.'https://github.com/stacks-network/stacks-core.git'] +clarity = { path = "../stacks-core/clarity" } +stacks-common = { path = "../stacks-core/stacks-common" } +stackslib = { path = "../stacks-core/stackslib" } diff --git a/components/clarinet-sdk-wasm/src/core.rs b/components/clarinet-sdk-wasm/src/core.rs index 59ce50293..a7f4a47d2 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(), } } @@ -1075,13 +1076,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 6de4a9dbc..690220117 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 f00a0fced..66424516e 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,