Skip to content

Commit f5bdea9

Browse files
committed
Initial commit; WIP; non-stable API
1 parent 7cedf9a commit f5bdea9

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Continuous Integration
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- uses: aiken-lang/setup-aiken@v1
14+
with:
15+
version: v1.1.9
16+
- run: aiken fmt --check
17+
- run: aiken check -D
18+
- run: aiken build

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Aiken compilation artifacts
2+
artifacts/
3+
# Aiken's project working directory
4+
build/
5+
# Aiken's default documentation export
6+
docs/

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<div align="center">
2+
<hr />
3+
<h2 align="center" style="border-bottom: none"><img style="position: relative; top: 0.25rem;" src="https://raw.githubusercontent.com/aiken-lang/branding/main/assets/icon.png" alt="Aiken" height="30" /> aiken/bench</h2>
4+
5+
[![Licence](https://img.shields.io/github/license/aiken-lang/bench?style=for-the-badge)](https://github.com/aiken-lang/bench/blob/main/LICENSE)
6+
[![Continuous Integration](https://img.shields.io/github/actions/workflow/status/aiken-lang/bench/continuous-integration.yml?style=for-the-badge)](https://github.com/aiken-lang/bench/actions/workflows/continuous-integration.yml)
7+
<hr/>
8+
</div>
9+
10+
The official library for writing _samplers_ (a.k.a Scaled Fuzzers) for the [Aiken](https://aiken-lang.org) Cardano smart-contract language.
11+
12+
> ### ⚠️ WARNING
13+
>
14+
> **IMPORTANT:** This is a work in progress and the API is not stable yet; additionally Samplers are not yet supported in the current version of Aiken (v1.1.9).
15+
16+
## Installation
17+
18+
```
19+
aiken add aiken-lang/bench --version v0.0.0
20+
```
21+
22+
## Getting started
23+
24+
First, make sure you have the [Aiken's user manual about tests](https://aiken-lang.org/language-tour/tests#property-based-test); in particular the section about benchmarking functions.
25+
26+
In many situations, you can use primitives from this library out-of-the-box, composing them inline when necessary. For example, if you need a growing non-empty list of values, you can simply write:
27+
28+
```
29+
use aiken/bench
30+
31+
bench my_bench(xs via bench.list(bench.int(Linear(1)), Linear(1))) {
32+
// some function
33+
}
34+
```
35+
36+
You can also write your own more complex sampler. Note that writing good samplers can be complicated, so here are a few guiding principles you should follow.. TODO

lib/aiken/sample.ak

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
use aiken/builtin
2+
use cardano/assets.{Value}
3+
use aiken/interval.{Interval}
4+
use cardano/script_context.{ScriptContext}
5+
use cardano/transaction.{Input, Output, ScriptPurpose, Transaction}
6+
use aiken/crypto.{Blake2b_224, Hash}
7+
use cardano/address.{Address, Credential, Inline, VerificationKey}
8+
use aiken/collection/dict
9+
10+
/// A growth pattern determines how complexity scales with input size
11+
pub type Growth {
12+
/// Constant growth: f(n) = c
13+
Constant
14+
/// Linear growth: f(n) = n
15+
Linear(Int)
16+
/// Exponential growth: f(n) = 2^n
17+
Exponential(Int)
18+
/// Logarithmic growth: f(n) = log(n)
19+
Logarithmic(Int)
20+
}
21+
22+
/// Core random number generator that produces a byte (0-255)
23+
fn rand(prng: PRNG) -> Option<(PRNG, Int)> {
24+
when prng is {
25+
Seeded { seed, choices } -> {
26+
let choice = builtin.index_bytearray(seed, 0)
27+
Some((
28+
Seeded {
29+
seed: crypto.blake2b_256(seed),
30+
choices: builtin.cons_bytearray(choice, choices),
31+
},
32+
choice,
33+
))
34+
}
35+
Replayed { cursor, choices } -> {
36+
if cursor >= 1 {
37+
let cursor = cursor - 1
38+
Some((
39+
Replayed { cursor, choices },
40+
builtin.index_bytearray(choices, cursor),
41+
))
42+
} else {
43+
None
44+
}
45+
}
46+
}
47+
}
48+
49+
/// Apply a growth pattern to scale an input size
50+
pub fn apply_growth(pattern: Growth, n: Int) -> Int {
51+
when pattern is {
52+
Constant -> 1
53+
Linear(base) -> base * n
54+
Exponential(base) ->
55+
if n <= 0 {
56+
0
57+
} else {
58+
exp_int_helper(base, n, 1)
59+
}
60+
Logarithmic(base) ->
61+
if n <= 1 {
62+
0
63+
} else {
64+
count_divisions(base, n, 0)
65+
}
66+
}
67+
}
68+
69+
// TODO: Probably need to use a builtin in the future.
70+
fn exp_int_helper(base: Int, n: Int, acc: Int) -> Int {
71+
if base <= 0 {
72+
acc
73+
} else {
74+
exp_int_helper(base - 1, n, acc * n)
75+
}
76+
}
77+
78+
// TODO: Probably need to use a builtin in the future.
79+
fn count_divisions(base: Int, n: Int, acc: Int) -> Int {
80+
if n <= 1 {
81+
acc
82+
} else {
83+
count_divisions(base, n / base, acc + 1)
84+
}
85+
}
86+
87+
// SAMPLERS:
88+
89+
/// Create a constant sampler that always returns the same value
90+
pub fn constant(x: a) -> Sampler<a> {
91+
fn(_n) { fn(prng) { Some((prng, x)) } }
92+
}
93+
94+
/// Create a sampler that generates integers with configurable growth
95+
pub fn int(growth: Growth) -> Sampler<Int> {
96+
fn(n) -> Fuzzer<Int> {
97+
let scaled_multiplier = apply_growth(growth, n)
98+
fn(prng) {
99+
when rand(prng) is {
100+
Some((new_prng, value)) -> Some((new_prng, value * scaled_multiplier))
101+
None -> None
102+
}
103+
}
104+
}
105+
}
106+
107+
/// Generate bytestrings with length scaling according to the growth pattern
108+
pub fn bytestring(growth: Growth) -> Sampler<ByteArray> {
109+
fn(n) {
110+
let length_fuzzer = int(growth)(n)
111+
fn(prng) {
112+
when length_fuzzer(prng) is {
113+
Some((prng2, len)) -> {
114+
todo
115+
}
116+
None -> None
117+
}
118+
}
119+
}
120+
}
121+
122+
fn list_handler(items_so_far: List<a>, i: Int, scaled_length: Int, fuzzer: Fuzzer<a>, prng: PRNG) -> Option<(PRNG, List<a>)> {
123+
if i > scaled_length {
124+
Some((prng, items_so_far))
125+
} else {
126+
when fuzzer(prng) is {
127+
Some((new_prng, item)) ->
128+
list_handler([item, ..items_so_far], i + 1, scaled_length, fuzzer, new_prng)
129+
None -> None
130+
}
131+
}
132+
}
133+
134+
/// Create a sampler that generates lists with configurable growth
135+
pub fn list(element_sampler: Sampler<a>, growth: Growth) -> Sampler<List<a>> {
136+
fn(n) {
137+
let scaled_length = apply_growth(growth, n)
138+
let element_fuzzer = element_sampler(n)
139+
fn(prng) {
140+
list_handler([], 0, scaled_length, element_fuzzer, prng)
141+
}
142+
}
143+
}
144+
145+
pub fn pair(first_sampler: Sampler<a>, second_sampler: Sampler<b>) -> Sampler<Pair<a, b>> {
146+
fn(n) {
147+
let first_fuzzer = first_sampler(n)
148+
let second_fuzzer = second_sampler(n)
149+
fn(prng) {
150+
when first_fuzzer(prng) is {
151+
Some((prng2, first_val)) -> {
152+
when second_fuzzer(prng2) is {
153+
Some((prng3, second_val)) ->
154+
Some((prng3, Pair(first_val, second_val)))
155+
None -> None
156+
}
157+
}
158+
None -> None
159+
}
160+
}
161+
}
162+
}
163+
164+
pub fn pairs(first_sampler: Sampler<a>, second_sampler: Sampler<b>, growth: Growth) {
165+
fn (n) {
166+
let scaled_length = apply_growth(growth, n)
167+
let pair_sampler = pair(first_sampler, second_sampler)(n)
168+
169+
fn(prng) {
170+
list_handler([], 0, scaled_length, pair_sampler, prng)
171+
}
172+
}
173+
}
174+
175+
pub fn dict(key_sampler: Sampler<ByteArray>, value_sampler: Sampler<a>, growth: Growth) {
176+
fn (n) {
177+
let scaled_length = apply_growth(growth, n)
178+
map(pairs(key_sampler, value_sampler, growth), fn(pairs) {
179+
dict.from_pairs(pairs)
180+
})
181+
}
182+
}
183+
184+
pub fn map(sampler: Sampler<a>, f: fn(a) -> b) -> Sampler<b> {
185+
fn(n) {
186+
let fuzzer = sampler(n)
187+
fn(prng) {
188+
when fuzzer(prng) is {
189+
Some((prng2, a)) -> Some((prng2, f(a)))
190+
None -> None
191+
}
192+
}
193+
}
194+
}
195+
196+
pub fn map2(sampler: Sampler<a>, sampler2: Sampler<b>, f: fn(a, b) -> c) -> Sampler<c> {
197+
fn(n) {
198+
let fuzzer1 = sampler(n)
199+
let fuzzer2 = sampler2(n)
200+
fn(prng) {
201+
when fuzzer1(prng) is {
202+
Some((prng2, a)) -> {
203+
when fuzzer2(prng2) is {
204+
Some((prng3, b)) -> Some((prng3, f(a, b)))
205+
None -> None
206+
}
207+
}
208+
None -> None
209+
}
210+
}
211+
}
212+
}

0 commit comments

Comments
 (0)