From 46c357df7b9c39b3df2a9312e6e8fd37bca7c661 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 25 Feb 2024 12:16:07 +0100 Subject: [PATCH] Fix Int/BigInt pivot We've been wrongly representing large ints as BigInt, causing them to behave differently in the VM through builtins like 'serialise_data'. Indeed, we expect anything that fits in 8 bytes to be encoded as Major Type 0 or 1. But we were switching to encoding as Major type 6 (tagged, PosBigInt, NegBigInt) for much smaller values! Anything outside of the range [-2^32, 2^32-1] would be treated as big int (positive or negative). Why? Because we checked whether a value i would fit in an i64, and if it didn't we treated it as big int. But the reality is more subtle... Fortunately, Rust has i128 and the minicbor library implements TryFrom which enforces that the value fits in a range of [-2^64, 2^64 - 1], so we're back on track easily. --- CHANGELOG.md | 5 +++-- crates/uplc/src/ast.rs | 6 +++--- crates/uplc/src/machine/value.rs | 12 ++++++++---- examples/acceptance_tests/094/aiken.lock | 7 +++++++ examples/acceptance_tests/094/aiken.toml | 3 +++ examples/acceptance_tests/094/lib/foo.ak | 9 +++++++++ 6 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 examples/acceptance_tests/094/aiken.lock create mode 100644 examples/acceptance_tests/094/aiken.toml create mode 100644 examples/acceptance_tests/094/lib/foo.ak diff --git a/CHANGELOG.md b/CHANGELOG.md index 8760b4cc..eb6b9e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,14 @@ # Changelog -## v1.0.25-alpha - +## v1.0.25-alpha - UNRELEASED ### Added -- **aiken-lang**: Data now has a generic argument that can be used to specify the blueprint type. @KtorZ +- **aiken-lang**: Data now has a generic argument that can be used to specify the blueprint type. @KtorZ ### Fixed +- **uplc**: `serialise_data` builtin wrongly encoding some larger ints as tagged CBOR bigints, instead of plain integers over 9 bytes. @KtorZ ### Changed diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 1fd3736a..e4e502cd 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -281,9 +281,9 @@ impl Data { hex::encode(bytes) } pub fn integer(i: BigInt) -> PlutusData { - match i.to_i64() { - Some(i) => PlutusData::BigInt(alonzo::BigInt::Int(i.into())), - None => { + match i.to_i128().map(|n| n.try_into()) { + Some(Ok(i)) => PlutusData::BigInt(alonzo::BigInt::Int(i)), + _ => { let (sign, bytes) = i.to_bytes_be(); match sign { num_bigint::Sign::Minus => { diff --git a/crates/uplc/src/machine/value.rs b/crates/uplc/src/machine/value.rs index 37cac1fc..4b8a3228 100644 --- a/crates/uplc/src/machine/value.rs +++ b/crates/uplc/src/machine/value.rs @@ -410,10 +410,14 @@ pub fn from_pallas_bigint(n: &babbage::BigInt) -> BigInt { } pub fn to_pallas_bigint(n: &BigInt) -> babbage::BigInt { - if let Some(i) = n.to_i64() { - let pallas_int: pallas::codec::utils::Int = i.into(); - babbage::BigInt::Int(pallas_int) - } else if n.is_positive() { + if let Some(i) = n.to_i128() { + if let Ok(i) = i.try_into() { + let pallas_int: pallas::codec::utils::Int = i; + return babbage::BigInt::Int(pallas_int); + } + } + + if n.is_positive() { let (_, bytes) = n.to_bytes_be(); babbage::BigInt::BigUInt(bytes.into()) } else { diff --git a/examples/acceptance_tests/094/aiken.lock b/examples/acceptance_tests/094/aiken.lock new file mode 100644 index 00000000..6e350cda --- /dev/null +++ b/examples/acceptance_tests/094/aiken.lock @@ -0,0 +1,7 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +requirements = [] +packages = [] + +[etags] diff --git a/examples/acceptance_tests/094/aiken.toml b/examples/acceptance_tests/094/aiken.toml new file mode 100644 index 00000000..e933c285 --- /dev/null +++ b/examples/acceptance_tests/094/aiken.toml @@ -0,0 +1,3 @@ +name = "aiken-lang/acceptance_test_094" +version = "0.0.0" +description = "" diff --git a/examples/acceptance_tests/094/lib/foo.ak b/examples/acceptance_tests/094/lib/foo.ak new file mode 100644 index 00000000..54b57b97 --- /dev/null +++ b/examples/acceptance_tests/094/lib/foo.ak @@ -0,0 +1,9 @@ +use aiken/builtin + +test u32_boundary_down() { + builtin.serialise_data(0xdeadbeefdeadbeef) == #"1bdeadbeefdeadbeef" +} + +test u32_boundary_up() { + builtin.serialise_data(-0xdeadbeefdeadbeef) == #"3bdeadbeefdeadbeee" +}