Skip to content

Commit

Permalink
added estimate costs for Clarinet and Clarinet JS SDK pages
Browse files Browse the repository at this point in the history
  • Loading branch information
ECBSJ committed Aug 15, 2024
1 parent 1bf5dbe commit bb55cdd
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 9 deletions.
98 changes: 89 additions & 9 deletions content/docs/stacks/clarinet-js-sdk/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ title: Quickstart
description: Learn how to test a simple counter contract using the Clarinet JS SDK.
---

import { ChevronRight, Code, Terminal } from 'lucide-react';
import { File, Folder, Files } from 'fumadocs-ui/components/files';
import { Steps, Step } from 'fumadocs-ui/components/steps';
import { SmallCard } from '@/components/card';
import { ChevronRight, Code, Terminal } from "lucide-react"
import { File, Folder, Files } from "fumadocs-ui/components/files"
import { Steps, Step } from "fumadocs-ui/components/steps"
import { SmallCard } from "@/components/card"

In this quickstart guide, you will initialize a simulated development network for testing a smart contract using the Clarinet JS SDK. You'll learn how to initialize your project, interact with a smart contract, call its functions, and test the results.

Expand Down Expand Up @@ -41,7 +41,7 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
```

The `Cl` namespace simplifies the process of creating and handling Clarity values. This functionality is particularly useful in testing environments where developers need to simulate contract interactions accurately.

</Step>
<Step>
## Retrieve an account from the simnet
Expand All @@ -60,6 +60,7 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
<Callout title="Note">
The `--stacks` flag is required and allows you to specify the network to scan. Other options include `--bitcoin`.
</Callout>

</Step>
<Step>
## Write your first test
Expand All @@ -80,6 +81,7 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
});
});
```

</Step>
<Step>
## Test the count-up function
Expand All @@ -91,7 +93,7 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
it("increments the count of the user's principal by 1", () => {
const countUpCall = simnet.callPublicFn("counter", "count-up", [], wallet);
expect(countUpCall.result).toBeOk(Cl.bool(true)); // [!code highlight]
const getCountCall = simnet.callReadOnlyFn(
"counter",
"get-count",
Expand All @@ -108,12 +110,13 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
<Callout title="Custom matchers">
<p>The <code>toBeOk</code> and <code>toBeUint</code> methods are used to ensure the `count-up` function returns the proper Clarity values. For more details, check out the [custom matchers](/stacks/clarinet-js-sdk/references/custom-matchers) page.</p>
</Callout>

</Step>
<Step>
## Run your tests

Every generated project comes with a <code>package.json</code> file that contains the necessary dependencies and scripts to run your tests.

```json title="package.json"
"scripts": {
"test": "vitest run", // [!code highlight]
Expand All @@ -123,10 +126,87 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
```

You can now run your tests, with your preferred package manager, by executing the following command:

```console
npm run test
```

</Step>
<Step>
## Run your tests with code coverage and cost analysis

Clarinet can automatically also produce a code coverage report and cost analysis on the test you ran.

```json title="package.json"
"scripts": {
"test": "vitest run",
"test:report": "vitest run -- --coverage --costs", // [!code highlight]
"test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:report\""
}
```

By running the `npm run test:report` command in your terminal, an `lcov.info` and `costs-reports.json` file will appear in your root folder:

<Files className='pointer-events-none'>
<Folder name="contracts" defaultOpen>
<File name="counter.clar" />
</Folder>
<Folder name="settings" />
<Folder name="tests" defaultOpen>
<File name="counter.test.ts" />
</Folder>
<File name=".gitignore" />
<File name="Clarinet.toml" />
<File name="costs-reports.json" className='bg-[hsl(var(--highlight))]' />
<File name="lcov.info" className='bg-[hsl(var(--highlight))]' />
<File name="package.json" />
<File name="tsconfig.json" />
<File name="vitest.config.js" />
</Files>

The `costs-reports.json` file will output the cost analysis for every function ran in your tests. Since the `count-up` function was used in our test, this is how the cost analysis output would look like for that function:

```json title="costs-reports.json"
{
"test_name": "tests/counter.test.ts__counter contract__increments the count of the user's principal by 1",
"contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.counter",
"method": "count-up",
"args": [],
"cost_result": {
"total": {
"write_length": 41,
"write_count": 1,
"read_length": 245,
"read_count": 5,
"runtime": 4752
},
"limit": {
"write_length": 15000000,
"write_count": 15000,
"read_length": 100000000,
"read_count": 15000,
"runtime": 5000000000
},
"memory": 40,
"memory_limit": 100000000
}
}
```

To access and read the `lcov.info` file in a human-readable format, we can first install the `lcov` [package](https://github.com/linux-test-project/lcov) that helps generate a graphical front-end for coverage testing tools.

```console title="Terminal"
brew install lcov
```

Then we'll run the `genhtml` command below with the following options. This command and its options will generate the actual html file, add in additional branch coverage, and will automatically store the files in a new folder called `coverage`.

```console title="Terminal"
genhtml lcov.info --branch-coverage -o coverage
```

You can then navigate into the new `coverage` folder and run the command `open index.html` to view your coverage report. In addition, a summary of the coverage report will be outputted in the terminal.

</Step>
</Steps>

Expand All @@ -150,4 +230,4 @@ Check out the [methods](/stacks/clarinet-js-sdk/references/methods) page for the
title="Migrating your tests to Clarinet SDK"
description="Learn how to migrate your existing Clarinet projects to the Clarinet JS SDK"
/>
</Cards>
</Cards>
85 changes: 85 additions & 0 deletions content/docs/stacks/clarinet/guides/estimate-costs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
title: Estimating contract costs
description: Analyze execution costs pertaining to your contract.
---

import { File, Folder, Files } from "fumadocs-ui/components/files"

Transactions on the Stacks blockchain comes with execution costs that can dictate transaction fees for your users. To analyze the execution costs of your contract's functions, let's continue with the simple `counter` contract from the [Clarinet quickstart](/stacks/clarinet/quickstart) as an example.

```clarity
(define-map Counters principal uint)
(define-read-only (get-count (who principal))
(default-to u0 (map-get? Counters who))
)
(define-public (count-up)
(ok (map-set Counters tx-sender (+ (get-count tx-sender) u1)))
)
```

To start analyzing execution costs , first run the `clarinet console` command inside your project.

```console title="Terminal"
clarinet console
```

## Get Costs

The `::get_costs <expr>` command in the Clarinet console will display the cost analysis of the given expression as well as the return value of the given expression. The `::get_costs` command expects an expression, so make a `contract-call?` with our `count-up` function and see what happens.

```console title="Terminal"
::get_costs (contract-call? .counter count-up)
```

We should see a chart breaking down the different types of execution costs in the console. Here's the execution cost analysis returned from running the above command:

```console
>> ::get_costs (contract-call? .counter count-up)
+----------------------+-------------+----------------+-----------------+
| | Consumed | Limit | Percentage |
+----------------------+-------------+----------------+-----------------+
| Runtime | 4752 | 5000000000 | 0.00 % |
+----------------------+-------------+----------------+-----------------+
| Read count | 5 | 15000 | 0.03 % |
+----------------------+-------------+----------------+-----------------+
| Read length (bytes) | 245 | 100000000 | 0.00 % |
+----------------------+-------------+----------------+-----------------+
| Write count | 1 | 15000 | 0.01 % |
+----------------------+-------------+----------------+-----------------+
| Write length (bytes) | 41 | 15000000 | 0.00 % |
+----------------------+-------------+----------------+-----------------+


(ok true)
```

As you can see from the chart, execution costs are broken into 5 different categories, each with its own limit. The limit is pertaining to what each Stacks block is limited to.

<Callout title="Execution Cost Categories">
The below lists out each of the 5 different categories of Clarity execution costs. ([source](https://book.clarity-lang.org/ch12-00-runtime-cost-analysis.html))

- **Runtime** costs limits overall complexity of the code that can be executed. For example, negating a boolean value is less complex than calculating SHA512 hash, therefore (not false) will consume less runtime costs than (sha512 "hello world") . This category is also affected by contract size.
- **Read count** limits how many times we can read from memory or chain state to a extract piece of information. It is affected by reading constants, variables, intermediate variables created with let , maps, but also by some functions that needs to save intermediate results during execution.
- **Read length (bytes)** limits how much data we can read from memory or the chain. It is also affected by contract size. Calling into a contract using contract-call? Increases the read length by an amount equal to the contract size in bytes, every time. If you call into a contract with a length of 2, 000 bytes twice, the read length is increased twice.
- **Write count** limits how many times we can write data into chain. It increments when writing to variables and maps.
- **Write length (bytes)** limits how much data we can write to the chain.

</Callout>

The percentage column shows what percentage of the execution costs your expression consumed out of the block limit given.

## Toggle costs

You can toggle the cost analysis to always be displayed after every expression execution in the console by toggling this feature on with the command `::toggle_costs`.

```console title="Terminal"
::toggle_costs
```

To turn it off, just run the `::toggle_costs` command again.

Check out the Clarity Book section on [runtime cost analysis](https://book.clarity-lang.org/ch12-00-runtime-cost-analysis.html) to read up on cost optimization techniques and recommendations.

To see how you can run automated cost analysis on your unit tests, check out our [quickstart](/stacks/clarinet-js-sdk/quickstart#run-your-tests-with-code-coverage-and-cost-analysis) section for the Clarinet JS SDK.
1 change: 1 addition & 0 deletions content/docs/stacks/clarinet/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"guides/add-a-contract",
"guides/validate-a-contract",
"guides/debug-a-contract",
"guides/estimate-costs",
"guides/run-a-local-devnet",
"guides/deploy-a-contract",
"guides/create-deployment-plans"
Expand Down

0 comments on commit bb55cdd

Please sign in to comment.