diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e5fb7..cc9cbad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - Extended functions for retrieving information from `OP_RETURN`. +- Conversion and bit functions for `u2`. ### Changed - Bumped Simplex to 0.2.0. diff --git a/README.md b/README.md index cb1e5b8..f3a9c31 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,11 @@ simf/lib ├── u1 │ └── convert.simf │ └── Conversions from `u1` to other uint types and `bool`. +├── u2 +│ ├── bit.simf +│ │ └── Basic bitwise operations (`and`, `or`, `eq`). +│ └── convert.simf +│ └── Conversions between `u2` and other uint types. ├── u8 ├── u16 ├── u32 diff --git a/docs/stdlib.json b/docs/stdlib.json index 0276c56..38d1a21 100644 --- a/docs/stdlib.json +++ b/docs/stdlib.json @@ -748,6 +748,83 @@ "output_type": "bool", "description": "Convert a `u1` value to `bool`." }, + { + "simplicityhl_name": "and_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "u2", + "description": "Bitwise AND of two `u2` values." + }, + { + "simplicityhl_name": "or_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "u2", + "description": "Bitwise OR of two `u2` values." + }, + { + "simplicityhl_name": "eq_2", + "section": "`u2` bit logic", + "input_type": "u2, u2", + "output_type": "bool", + "description": "Check whether two `u2` values are equal." + }, + { + "simplicityhl_name": "u2_to_u8", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u8", + "description": "Widen a `u2` value to a `u8` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u16", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u16", + "description": "Widen a `u2` value to a `u16` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u32", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u32", + "description": "Widen a `u2` value to a `u32` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u64", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u64", + "description": "Widen a `u2` value to a `u64` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u128", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u128", + "description": "Widen a `u2` value to a `u128` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "u2_to_u256", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u256", + "description": "Widen a `u2` value to a `u256` value, zero-extending the high bits." + }, + { + "simplicityhl_name": "split_u2_into_u1", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "(u1, u1)", + "description": "Split a `u2` value into two `u1` words, most-significant first." + }, + { + "simplicityhl_name": "safe_u2_to_u1", + "section": "`u2` conversions", + "input_type": "u2", + "output_type": "u1", + "description": "Narrow a `u2` value to a `u1` value.\n\n## Panics\nPanics if the value does not fit in `u1`." + }, { "simplicityhl_name": "checked_add_8", "section": "`u8` arithmetic", diff --git a/simf/lib/u2/bit.simf b/simf/lib/u2/bit.simf new file mode 100644 index 0000000..e4671ba --- /dev/null +++ b/simf/lib/u2/bit.simf @@ -0,0 +1,25 @@ +use crate::lib::binary::and; + +/// Bitwise AND of two 2-bit values +pub fn and_2(a: u2, b: u2) -> u2 { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + <(u1, u1)>::into((jet::and_1(a_high, b_high), jet::and_1(a_low, b_low))) +} + +/// Bitwise OR of two 2-bit values +pub fn or_2(a: u2, b: u2) -> u2 { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + <(u1, u1)>::into((jet::or_1(a_high, b_high), jet::or_1(a_low, b_low))) +} + +/// Checks if two 2-bit values are equal +pub fn eq_2(a: u2, b: u2) -> bool { + let (a_high, a_low): (u1, u1) = ::into(a); + let (b_high, b_low): (u1, u1) = ::into(b); + + and(jet::eq_1(a_high, b_high), jet::eq_1(a_low, b_low)) +} diff --git a/simf/lib/u2/convert.simf b/simf/lib/u2/convert.simf new file mode 100644 index 0000000..27251a6 --- /dev/null +++ b/simf/lib/u2/convert.simf @@ -0,0 +1,60 @@ +/// Widening uint conversions + +/// Converts u2 to u8 +pub fn u2_to_u8(a: u2) -> u8 { + <(u2, u2, u2, u2)>::into((0, 0, 0, a)) +} + +/// Converts u2 to u16 +pub fn u2_to_u16(a: u2) -> u16 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_16(a_u8) +} + +/// Converts u2 to u32 +pub fn u2_to_u32(a: u2) -> u32 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_32(a_u8) +} + +/// Converts u2 to u64 +pub fn u2_to_u64(a: u2) -> u64 { + let a_u8: u8 = u2_to_u8(a); + + jet::left_pad_low_8_64(a_u8) +} + +/// Converts u2 to u128 +pub fn u2_to_u128(a: u2) -> u128 { + let a_u64: u64 = u2_to_u64(a); + + <(u64, u64)>::into((0, a_u64)) +} + +/// Converts u2 to u256 +pub fn u2_to_u256(a: u2) -> u256 { + let a_u128: u128 = u2_to_u128(a); + + <(u128, u128)>::into((0, a_u128)) +} + +/// Splitting uint conversions + +/// Splits u2 into two u1 +pub fn split_u2_into_u1(a: u2) -> (u1, u1) { + ::into(a) +} + +/// Narrowing uint conversions + +/// Converts u2 into u1. +/// Panics if the value does not fit in u1 +pub fn safe_u2_to_u1(a: u2) -> u1 { + let (high, low): (u1, u1) = ::into(a); + + assert!(jet::eq_1(high, 0)); + + low +} diff --git a/simf/tests/u2/bit.simf b/simf/tests/u2/bit.simf new file mode 100644 index 0000000..c15887b --- /dev/null +++ b/simf/tests/u2/bit.simf @@ -0,0 +1,33 @@ +use crate::lib::u2::bit::{and_2, or_2, eq_2}; + +use crate::tests::support::dispatch::is_selected; +use crate::tests::support::expect::assert_bool; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u2 = witness::FIRST_ARG; + let b: u2 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let expected_bool: bool = witness::EXPECTED_BOOL; + + match is_selected(0, fn_idx) { + true => { + assert!(eq_2(and_2(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(1, fn_idx) { + true => { + assert!(eq_2(or_2(a, b), unwrap(expected))); + }, + false => (), + }; + match is_selected(2, fn_idx) { + true => { + assert_bool(eq_2(a, b), expected_bool); + }, + false => (), + }; +} diff --git a/simf/tests/u2/convert.simf b/simf/tests/u2/convert.simf new file mode 100644 index 0000000..486bf8f --- /dev/null +++ b/simf/tests/u2/convert.simf @@ -0,0 +1,102 @@ +use crate::lib::u2::convert::{ + u2_to_u8, + u2_to_u16, + u2_to_u32, + u2_to_u64, + u2_to_u128, + u2_to_u256, + split_u2_into_u1, + safe_u2_to_u1 +}; +use crate::lib::asserts::{ + assert_eq_1, + assert_eq_8, + assert_eq_16, + assert_eq_32, + assert_eq_64, + assert_eq_128, + assert_eq_256 +}; + +use crate::tests::support::dispatch::is_selected; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u2 = witness::FIRST_ARG; + + let expected: u256 = witness::EXPECTED; + let (_, _, _, expected_64): (u64, u64, u64, u64) = ::into(expected); + + match is_selected(0, fn_idx) { + true => { + let expected: u8 = jet::rightmost_64_8(expected_64); + + assert_eq_8(u2_to_u8(a), expected); + }, + false => (), + }; + + match is_selected(1, fn_idx) { + true => { + let expected: u16 = jet::rightmost_64_16(expected_64); + + assert_eq_16(u2_to_u16(a), expected); + }, + false => (), + }; + + match is_selected(2, fn_idx) { + true => { + let expected: u32 = jet::rightmost_64_32(expected_64); + + assert_eq_32(u2_to_u32(a), expected); + }, + false => (), + }; + + match is_selected(3, fn_idx) { + true => { + assert_eq_64(u2_to_u64(a), expected_64); + }, + false => (), + }; + + match is_selected(4, fn_idx) { + true => { + let (_, expected): (u128, u128) = ::into(expected); + + assert_eq_128(u2_to_u128(a), expected); + }, + false => (), + }; + + match is_selected(5, fn_idx) { + true => { + assert_eq_256(u2_to_u256(a), expected); + }, + false => (), + }; + + match is_selected(6, fn_idx) { + true => { + let (a1, a0): (u1, u1) = split_u2_into_u1(a); + + let expected: u2 = jet::rightmost_64_2(expected_64); + let (expected1, expected0): (u1, u1) = ::into(expected); + + assert_eq_1(a1, expected1); + assert_eq_1(a0, expected0); + }, + false => (), + }; + + match is_selected(7, fn_idx) { + true => { + let expected: u1 = jet::rightmost_64_1(expected_64); + + assert_eq_1(safe_u2_to_u1(a), expected); + }, + false => (), + }; +} diff --git a/simf/tests/utxo/op_return.simf b/simf/tests/utxo/op_return.simf index f5c9702..94e83a9 100644 --- a/simf/tests/utxo/op_return.simf +++ b/simf/tests/utxo/op_return.simf @@ -14,21 +14,13 @@ use crate::lib::utxo::op_return::{ assert_has_entry }; -use crate::lib::binary::and; +use crate::lib::u2::bit::eq_2; use crate::lib::asserts::assert_eq_8; use crate::lib::u8::convert::safe_u8_to_u1; use crate::tests::support::dispatch::is_selected; use crate::tests::support::expect::assert_bool; -// todo: move to lib -fn eq_2(a: u2, b: u2) -> bool { - let (a_high, a_low): (u1, u1) = ::into(a); - let (b_high, b_low): (u1, u1) = ::into(b); - - and(jet::eq_1(a_high, b_high), jet::eq_1(a_low, b_low)) -} - // todo: move to lib fn eq_4(a: u4, b: u4) -> bool { let a_u8: u8 = <(u4, u4)>::into((0, a)); diff --git a/tests/stdlib/main.rs b/tests/stdlib/main.rs index f988ca3..b91a90a 100644 --- a/tests/stdlib/main.rs +++ b/tests/stdlib/main.rs @@ -6,6 +6,7 @@ mod secp256k1; mod u1; mod u128; mod u16; +mod u2; mod u256; mod u32; mod u64; diff --git a/tests/stdlib/u2/bit.rs b/tests/stdlib/u2/bit.rs new file mode 100644 index 0000000..b642949 --- /dev/null +++ b/tests/stdlib/u2/bit.rs @@ -0,0 +1,107 @@ +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u2::bit::BitProgram as U2TestBitProgram; +use simplicityhl_std::artifacts::tests::u2::bit::derived_bit::{ + BitArguments as U2TestBitArguments, BitWitness as U2TestBitWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + And2, + Or2, + Eq2, +} + +const U2_MAX: u8 = 3; + +fn program() -> U2TestBitProgram { + U2TestBitProgram::new(&U2TestBitArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U2TestBitWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U2TestBitWitness { + function_index: function as u8, + first_arg: 0, + second_arg: 0, + expected: None, + expected_bool: false, + }, + } +} + +impl Case { + /// The two operands, `first_arg` and `second_arg`. + fn args(mut self, a: u8, b: u8) -> Self { + self.witness.first_arg = a; + self.witness.second_arg = b; + self + } + + /// The u2 value the arm should return + fn expect(mut self, expected: u8) -> Self { + self.witness.expected = Some(expected); + self + } + + /// `expected_bool`: the boolean the arm should report. + fn expected_bool(mut self, expected_bool: bool) -> Self { + self.witness.expected_bool = expected_bool; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn and_2(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let b = rand::thread_rng().gen_range(0..=U2_MAX); + let result = a & b; + + case(And2).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn or_2(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let b = rand::thread_rng().gen_range(0..=U2_MAX); + let result = a | b; + + case(Or2).args(a, b).expect(result).run(&context) +} + +#[simplex::test] +fn eq_2_true(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(Eq2).args(a, a).expected_bool(true).run(&context) +} + +#[simplex::test] +fn eq_2_false(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + let mut b = rand::thread_rng().gen_range(0..=U2_MAX); + + while a == b { + b = rand::thread_rng().gen_range(0..=U2_MAX); + } + + case(Eq2).args(a, b).expected_bool(false).run(&context) +} diff --git a/tests/stdlib/u2/convert.rs b/tests/stdlib/u2/convert.rs new file mode 100644 index 0000000..b48d307 --- /dev/null +++ b/tests/stdlib/u2/convert.rs @@ -0,0 +1,157 @@ +use primitive_types::U256; +use rand::Rng; + +use crate::common::core::{Expect, run}; + +use simplicityhl_std::artifacts::tests::u2::convert::ConvertProgram as U2ConvertTestProgram; +use simplicityhl_std::artifacts::tests::u2::convert::derived_convert::{ + ConvertArguments as U2ConvertTestArguments, ConvertWitness as U2ConvertTestWitness, +}; + +use FunctionToTest::*; + +enum FunctionToTest { + U2ToU8, + U2ToU16, + U2ToU32, + U2ToU64, + U2ToU128, + U2ToU256, + SplitU2IntoU1, + SafeU2ToU1, +} + +const U2_MAX: u8 = 3; + +fn program() -> U2ConvertTestProgram { + U2ConvertTestProgram::new(&U2ConvertTestArguments {}) +} + +/// One dispatch arm of the contract, plus the witness it reads. +struct Case { + witness: U2ConvertTestWitness, +} + +fn case(function: FunctionToTest) -> Case { + Case { + witness: U2ConvertTestWitness { + function_index: function as u8, + first_arg: 0, + expected: [0; 32], + }, + } +} + +impl Case { + /// The operand, `first_arg`. + fn arg(mut self, a: u8) -> Self { + self.witness.first_arg = a; + self + } + + /// `expected`: the value the arm should produce. + fn expect(mut self, expected: [u8; 32]) -> Self { + self.witness.expected = expected; + self + } + + /// Fund, spend, and expect the spend to succeed. + fn run(self, context: &simplex::TestContext) -> anyhow::Result<()> { + self.expecting(context, Expect::Ok) + } + + /// Fund, spend, and expect `expect`. + fn expecting(self, context: &simplex::TestContext, expect: Expect) -> anyhow::Result<()> { + run(context, program(), self.witness, expect) + } +} + +#[simplex::test] +fn u2_to_u8(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU8) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u16(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU16) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u32(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU32) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU64) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u128(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU128) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn u2_to_u256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(U2ToU256) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn split_u2_into_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=U2_MAX); + + case(SplitU2IntoU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u2_to_u1(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=1); + + case(SafeU2ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .run(&context) +} + +#[simplex::test] +fn safe_u2_to_u1_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(2..=U2_MAX); + + case(SafeU2ToU1) + .arg(a) + .expect(U256::from(a).to_big_endian()) + .expecting(&context, Expect::AssertFailed) +} diff --git a/tests/stdlib/u2/mod.rs b/tests/stdlib/u2/mod.rs new file mode 100644 index 0000000..61da102 --- /dev/null +++ b/tests/stdlib/u2/mod.rs @@ -0,0 +1,2 @@ +mod bit; +mod convert;