Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generators necessary for creating transactions and scenarios. #22

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
441 changes: 441 additions & 0 deletions lib/aiken/fuzz/scenario.ak

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions lib/cardano/fuzz/address.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use aiken/fuzz.{and_then, bool, bytearray_between, int_between, map, map2}
use cardano/address.{Address, Credential, Inline, Script, VerificationKey}

/// Generate a random Credential
pub fn any_credential() -> Fuzzer<Credential> {
map2(
bool(),
bytearray_between(28, 28),
fn(coin_flip, key_hash) {
if coin_flip {
VerificationKey(key_hash)
} else {
Script(key_hash)
}
},
)
}

pub fn any_address() -> Fuzzer<Address> {
let address_type <- and_then(int_between(0, 5))
when address_type is {
0 -> {
let payment <- map(bytearray_between(28, 28))
Address {
payment_credential: VerificationKey(payment),
stake_credential: None,
}
}
1 -> {
let payment <- map(bytearray_between(28, 28))
Address { payment_credential: Script(payment), stake_credential: None }
}
2 -> {
let payment <- and_then(bytearray_between(28, 28))
let stake <- map(bytearray_between(28, 28))
Address {
payment_credential: VerificationKey(payment),
stake_credential: Some(Inline(VerificationKey(stake))),
}
}
3 -> {
let payment <- and_then(bytearray_between(28, 28))
let stake <- map(bytearray_between(28, 28))
Address {
payment_credential: VerificationKey(payment),
stake_credential: Some(Inline(Script(stake))),
}
}
4 -> {
let payment <- and_then(bytearray_between(28, 28))
let stake <- map(bytearray_between(28, 28))
Address {
payment_credential: Script(payment),
stake_credential: Some(Inline(VerificationKey(stake))),
}
}
5 -> {
let payment <- and_then(bytearray_between(28, 28))
let stake <- map(bytearray_between(28, 28))
Address {
payment_credential: Script(payment),
stake_credential: Some(Inline(Script(stake))),
}
}
_ -> fail @"unexpected address type"
}
}
42 changes: 42 additions & 0 deletions lib/cardano/fuzz/address.test.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use aiken/fuzz.{label}
use cardano/address.{Address, Inline, Script, VerificationKey}
use cardano/fuzz/address.{any_address, any_credential} as fuzz_address

test prop_any_credential(c via any_credential()) {
label(
when c is {
Script(_) -> @"Script"
VerificationKey(_) -> @"VerificationKey"
},
)
True
}

test prop_any_address(a via any_address()) {
label(
when a is {
Address { payment_credential: VerificationKey(_), stake_credential: None } ->
@"VerificationKey_NoStake"
Address { payment_credential: Script(_), stake_credential: None } ->
@"Script_NoStake"
Address {
payment_credential: VerificationKey(_),
stake_credential: Some(Inline(VerificationKey(_))),
} -> @"VerificationKey_InlineVerificationKey"
Address {
payment_credential: VerificationKey(_),
stake_credential: Some(Inline(Script(_))),
} -> @"VerificationKey_InlineScript"
Address {
payment_credential: Script(_),
stake_credential: Some(Inline(VerificationKey(_))),
} -> @"Script_InlineVerificationKey"
Address {
payment_credential: Script(_),
stake_credential: Some(Inline(Script(_))),
} -> @"Script_InlineScript"
_ -> fail @"Unexpected Address from any_address"
},
)
True
}
107 changes: 107 additions & 0 deletions lib/cardano/fuzz/assets.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use aiken/collection/list
use aiken/fuzz.{
and_then, bytearray_between, constant, int_at_least, int_between, list_between,
map,
}
use cardano/assets.{AssetName, PolicyId, Value, ada_asset_name, ada_policy_id}

pub fn any_asset_name() -> Fuzzer<AssetName> {
bytearray_between(0, 32)
}

pub fn any_policy_id() -> Fuzzer<PolicyId> {
bytearray_between(28, 28)
}

pub fn any_ada_only_value() -> Fuzzer<Value> {
map(int_at_least(1), assets.from_lovelace)
}

pub fn any_assets(policy_id: PolicyId, value: Value) -> Fuzzer<Value> {
let names <- and_then(list_between(any_asset_name(), 1, 3))
list.foldr(
names,
constant(value),
fn(asset_name, step) {
let value <- and_then(step)
let quantity <- map(int_at_least(1))
value |> assets.add(policy_id, asset_name, quantity)
},
)
}

pub fn any_value_with(forced_policy_id: PolicyId) -> Fuzzer<Value> {
let value <- and_then(any_ada_only_value())
let policies <- and_then(list_between(any_policy_id(), 0, 2))
list.foldr(
[forced_policy_id, ..policies],
constant(value),
fn(policy_id, step) {
let value <- and_then(step)
any_assets(policy_id, value)
},
)
}

pub fn any_value() -> Fuzzer<Value> {
let value <- and_then(any_ada_only_value())
let policies <- and_then(list_between(any_policy_id(), 0, 2))
list.foldr(
policies,
constant(value),
fn(policy_id, step) {
let value <- and_then(step)
any_assets(policy_id, value)
},
)
}

pub fn any_value_extending(value: Value) -> Fuzzer<Value> {
let extra_lovelace <- and_then(int_at_least(1))
let policies <- and_then(list_between(any_policy_id(), 0, 2))
list.foldr(
policies,
constant(assets.add(value, ada_policy_id, ada_asset_name, extra_lovelace)),
fn(policy_id, step) {
let value <- and_then(step)
any_assets(policy_id, value)
},
)
}

// Helper function to generate constrained assets for a single policy
fn any_constrained_assets(
policy_id: PolicyId,
asset_constraints: List<Pair<AssetName, Pair<Int, Int>>>,
initial_value: Value,
) -> Fuzzer<Value> {
list.foldr(
asset_constraints,
constant(initial_value),
fn(asset_constraint, step) {
let current_value <- and_then(step)
let asset_name = asset_constraint.1st
let min_quantity = asset_constraint.2nd.1st
let max_quantity = asset_constraint.2nd.2nd
let quantity <- map(int_between(min_quantity, max_quantity))
current_value |> assets.add(policy_id, asset_name, quantity)
},
)
}

// Updated any_constrained_value function using any_constrained_assets
pub fn any_constrained_value(
constraints: List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>>,
) -> Fuzzer<Value> {
let base_value <- and_then(any_ada_only_value())
list.foldr(
constraints,
constant(base_value),
fn(constraint, step) {
let value <- and_then(step)
let policy_id = constraint.1st
let asset_constraints = constraint.2nd
any_constrained_assets(policy_id, asset_constraints, value)
},
)
}
166 changes: 166 additions & 0 deletions lib/cardano/fuzz/assets.test.ak
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
use aiken/collection/dict
use aiken/collection/list
use aiken/fuzz.{and_then, int_at_least, int_between, list_between, map}
use cardano/assets.{AssetName, PolicyId, Value}
use cardano/fuzz/assets.{
any_asset_name, any_constrained_value, any_policy_id, any_value,
any_value_extending, any_value_with,
} as fuzz_assets

fn help_prop_any_value_with() -> Fuzzer<(assets.PolicyId, assets.Value)> {
let policy <- and_then(any_policy_id())
map(any_value_with(policy), fn(b) { (policy, b) })
}

test prop_any_value_with((policy, value) via help_prop_any_value_with()) {
assets.tokens(value, policy) != dict.empty
}

fn help_prop_any_value_extending() -> Fuzzer<(assets.Value, assets.Value)> {
let v <- and_then(any_value())
map(any_value_extending(v), fn(ve) { (v, ve) })
}

test prop_any_value_extending(
(value, value_extended) via help_prop_any_value_extending(),
) {
assets.reduce(
value,
True,
fn(p, a, i, r) { r && assets.quantity_of(value_extended, p, a) >= i },
)
}

/// Helper function to generate asset constraints for a single policy
fn any_asset_constraint() -> Fuzzer<Pair<AssetName, Pair<Int, Int>>> {
let min_quantity <- and_then(int_at_least(0))
let max_quantity <- and_then(int_between(min_quantity, min_quantity + 100))
map(any_asset_name(), fn(a) { Pair(a, Pair(min_quantity, max_quantity)) })
}

/// Helper function to generate constraints for a single policy
fn any_policy_constraints() -> Fuzzer<
Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>,
> {
let asset_constraints <- and_then(list_between(any_asset_constraint(), 1, 10))
map(any_policy_id(), fn(p) { Pair(p, asset_constraints) })
}

/// Helper function to generate a list of policy constraints
fn any_constraints() -> Fuzzer<
List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>>,
> {
list_between(any_policy_constraints(), 1, 5)
}

/// Fuzzer to generate constraints along with a constrained value
fn help_prop_any_constrained_value() -> Fuzzer<
Pair<List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>>, Value>,
> {
let constraints <- and_then(any_constraints())
let merged = merge_policy_constraints(constraints)
let a_c_v = any_constrained_value(merged)
map(a_c_v, fn(v) { Pair(merged, v) })
}

///
/// Function to merge multiple constraints for the same asset within the same policy
fn merge_asset_constraints(
asset_constraints: List<Pair<AssetName, Pair<Int, Int>>>,
) -> List<Pair<AssetName, Pair<Int, Int>>> {
let grouped =
list.foldr(
asset_constraints,
dict.empty,
fn(pair, acc) {
let Pair(asset_name, Pair(min, max)) = pair
when dict.pop(acc, asset_name) is {
(Some(Pair(existing_min, existing_max)), acc) -> {
let new_min =
if min < existing_min {
min
} else {
existing_min
}
let new_max =
if max > existing_max {
max
} else {
existing_max
}
dict.insert(acc, asset_name, Pair(new_min, new_max))
}
(None, acc) -> dict.insert(acc, asset_name, Pair(min, max))
}
},
)
dict.to_pairs(grouped)
}

/// Function to merge multiple PolicyIds and their asset constraints into unique PolicyIds with merged asset constraints
fn merge_policy_constraints(
constraints: List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>>,
) -> List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>> {
// Group all asset constraints by PolicyId
let grouped_policies =
list.foldr(
constraints,
dict.empty,
fn(pair, acc) {
let Pair(policy_id, asset_constraints) = pair
when dict.pop(acc, policy_id) is {
(Some(existing_constraints), acc) ->
dict.insert(
acc,
policy_id,
list.concat(asset_constraints, existing_constraints),
)
(None, acc) -> dict.insert(acc, policy_id, asset_constraints)
}
},
)

// For each PolicyId, merge its asset constraints
// let l_acc: List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>> = []
let merged_policies =
dict.foldr(
grouped_policies,
[],
fn(policy_id, asset_constraints, acc) {
let merged_assets = merge_asset_constraints(asset_constraints)
list.push(acc, Pair(policy_id, merged_assets))
},
)
merged_policies
}

///
fn verify_constraints(
constraints: List<Pair<PolicyId, List<Pair<AssetName, Pair<Int, Int>>>>>,
value: Value,
) -> Bool {
list.all(
constraints,
fn(constraint) {
let Pair(policy_id, asset_constraints) = constraint
let tokens = assets.tokens(value, policy_id)
list.all(
asset_constraints,
fn(asset_constraint) {
let Pair(asset_name, Pair(min, max)) = asset_constraint
when dict.get(tokens, asset_name) is {
Some(quantity) -> quantity >= min && quantity <= max
None -> min <= 0
}
},
)
},
)
}

test prop_any_constrained_value(
Pair(constraints, value) via help_prop_any_constrained_value(),
) {
// Assert that all constraints are satisfied
verify_constraints(constraints, value)
}
Loading
Loading