diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 9d24d91b..75b73ff5 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -1078,7 +1078,7 @@ impl<'a> CodeGenerator<'a> { ); // if only one constructor, no need to check - if data_type.constructors.len() > 1 { + if data_type.constructors.len() > 1 || *clause_properties.is_final_clause() { // push constructor Index let mut tag_stack = pattern_stack.empty_with_scope(); tag_stack.integer(index.to_string()); @@ -1527,7 +1527,6 @@ impl<'a> CodeGenerator<'a> { } => { let id = self.id_gen.next(); let constr_var_name = format!("{constr_name}_{id}"); - let data_type = builder::lookup_data_type_by_tipo(&self.data_types, tipo).unwrap(); let mut when_stack = pattern_stack.empty_with_scope(); @@ -1547,10 +1546,11 @@ impl<'a> CodeGenerator<'a> { &mut clause_properties, ); - if data_type.constructors.len() > 1 { - if final_clause { - pattern_stack.finally(when_stack); - } else { + let data_type = builder::lookup_data_type_by_tipo(&self.data_types, tipo); + if final_clause { + pattern_stack.finally(when_stack); + } else if let Some(data_type) = data_type { + if data_type.constructors.len() > 1 { let empty_stack = pattern_stack.empty_with_scope(); pattern_stack.clause_guard( constr_var_name.clone(), @@ -1558,9 +1558,17 @@ impl<'a> CodeGenerator<'a> { when_stack, empty_stack, ); + } else { + pattern_stack.merge_child(when_stack); } } else { - pattern_stack.merge_child(when_stack); + let empty_stack = pattern_stack.empty_with_scope(); + pattern_stack.clause_guard( + constr_var_name.clone(), + tipo.clone(), + when_stack, + empty_stack, + ) } Some(constr_var_name) diff --git a/examples/acceptance_tests/082/lib/tests.ak b/examples/acceptance_tests/082/lib/tests.ak index 4a9b4f81..61d4bb30 100644 --- a/examples/acceptance_tests/082/lib/tests.ak +++ b/examples/acceptance_tests/082/lib/tests.ak @@ -1,6 +1,24 @@ +use aiken/interval.{Finite, Interval, IntervalBound} use aiken/list +use aiken/time.{PosixTime} +use aiken/transaction.{ScriptContext, ValidityRange} use aiken/transaction/value.{Value} +// TODO added to the stdlib in #40 +pub fn count(self: List, predicate: fn(a) -> Bool) -> Int { + list.foldl( + self, + fn(item, total) { + if predicate(item) { + total + 1 + } else { + total + } + }, + 0, + ) +} + test foldl_value_test1() { let val1 = value.from_lovelace(1000000) let val2 = value.from_lovelace(2000000) @@ -14,16 +32,155 @@ test foldl_value_test1() { 2, ) } -// test foldl_value_test2() { -// let val1 = value.from_lovelace(1000000) -// let val2 = value.from_lovelace(2000000) -// let foo = -// fn(i: Value, acc: (Value, Int)) { -// let (v, int) = acc -// (value.add(i, v), int + 1) -// } -// list.foldl([val1, val2], foo, (value.from_lovelace(0), 0)) == ( -// value.from_lovelace(3000000), -// 2, -// ) -// } + +test foldl_value_test2() { + let val1 = value.from_lovelace(1000000) + let val2 = value.from_lovelace(2000000) + let foo = + fn(i: Value, acc: (Value, Int)) { + let (v, int) = acc + (value.add(i, v), int + 1) + } + list.foldl([val1, val2], foo, (value.from_lovelace(0), 0)) == ( + value.from_lovelace(3000000), + 2, + ) +} + +pub type NativeScript { + Signature { keyHash: ByteArray } + AllOf { scripts: List } + AnyOf { scripts: List } + AtLeast { required: Int, scripts: List } + Before { time: PosixTime } + After { time: PosixTime } +} + +pub fn satisfied( + script: NativeScript, + signatories: List, + validRange: ValidityRange, +) -> Bool { + when script is { + Signature { keyHash } -> list.has(signatories, keyHash) + AllOf { scripts } -> + list.all(scripts, fn(s) { satisfied(s, signatories, validRange) }) + AnyOf { scripts } -> + list.any(scripts, fn(s) { satisfied(s, signatories, validRange) }) + AtLeast { required, scripts } -> + required <= count( + scripts, + fn(s) { satisfied(s, signatories, validRange) }, + ) + Before { time } -> + when validRange.upper_bound.bound_type is { + Finite(hi) -> + if validRange.upper_bound.is_inclusive { + hi <= time + } else { + hi < time + } + _ -> False + } + + After { time } -> + when validRange.lower_bound is { + IntervalBound { bound_type: b, is_inclusive: False } -> { + expect Finite(lo) = b + + time < lo + } + IntervalBound { bound_type: b, is_inclusive: True } -> { + expect Finite(lo) = b + time <= lo + } + } + } + // After { time } -> + // when validRange.lower_bound.bound_type is { + // Finite(lo) -> + // if validRange.lower_bound.is_inclusive { + // time <= lo + // } else { + // time < lo + // } + // _ -> False + // } +} + +test satisfying() { + let keyHash1 = "key1" + let keyHash2 = "key2" + let keyHash3 = "key3" + let sig1 = Signature { keyHash: keyHash1 } + let sig2 = Signature { keyHash: keyHash2 } + let sig3 = Signature { keyHash: keyHash3 } + let allOf = AllOf { scripts: [sig1, sig2] } + let anyOf = AnyOf { scripts: [sig1, sig2] } + let atLeast = AtLeast { required: 2, scripts: [sig1, sig2, sig3] } + let before = Before { time: 10 } + let after = After { time: 10 } + let between = AllOf { scripts: [After { time: 10 }, Before { time: 15 }] } + let vesting = + AnyOf { + scripts: [ + AllOf { scripts: [before, sig1] }, + // clawback + AllOf { scripts: [after, sig2] }, + ], + } + // vested + let validRange = + fn(lo: Int, hi: Int) -> ValidityRange { + Interval { + lower_bound: IntervalBound { + bound_type: Finite(lo), + is_inclusive: True, + }, + upper_bound: IntervalBound { + bound_type: Finite(hi), + is_inclusive: False, + }, + } + } + // Helper method because ? binds more tightly than ! + let unsatisfied = + fn(n: NativeScript, s: List, v: ValidityRange) { + !satisfied(n, s, v) + } + list.and( + [ + satisfied(sig1, [keyHash1], validRange(0, 1))?, + satisfied(sig2, [keyHash1, keyHash2], validRange(0, 1))?, + satisfied(allOf, [keyHash1, keyHash2], validRange(0, 1))?, + satisfied(anyOf, [keyHash2], validRange(0, 1))?, + satisfied(atLeast, [keyHash2, keyHash3], validRange(0, 1))?, + satisfied(before, [], validRange(0, 5))?, + satisfied(after, [], validRange(15, 20))?, + satisfied(after, [], validRange(10, 15))?, + satisfied(between, [], validRange(12, 13))?, + satisfied(vesting, [keyHash1], validRange(0, 5))?, + satisfied(vesting, [keyHash2], validRange(15, 20))?, + unsatisfied(sig1, [keyHash2], validRange(0, 1))?, + unsatisfied(sig3, [keyHash1, keyHash2], validRange(0, 1))?, + unsatisfied(allOf, [keyHash1, keyHash3], validRange(0, 1))?, + unsatisfied(anyOf, [keyHash3], validRange(0, 1))?, + unsatisfied(atLeast, [keyHash2], validRange(0, 1))?, + unsatisfied(before, [], validRange(5, 15))?, + unsatisfied(before, [], validRange(5, 10))?, + unsatisfied(before, [], validRange(10, 10))?, + unsatisfied(after, [], validRange(5, 15))?, + unsatisfied(between, [], validRange(0, 5))?, + unsatisfied(between, [], validRange(0, 13))?, + unsatisfied(between, [], validRange(0, 20))?, + unsatisfied(between, [], validRange(13, 20))?, + unsatisfied(between, [], validRange(13, 15))?, + unsatisfied(between, [], validRange(15, 20))?, + unsatisfied(vesting, [keyHash2], validRange(0, 5))?, + unsatisfied(vesting, [keyHash1], validRange(15, 20))?, + unsatisfied(vesting, [keyHash3], validRange(10, 10))?, + unsatisfied(vesting, [keyHash3], validRange(0, 5))?, + unsatisfied(vesting, [keyHash3], validRange(15, 20))?, + ], + ) +}