diff --git a/Cargo.toml b/Cargo.toml index f012438..7922de8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ cc = "1.0" ckb-types = { package = "ckb-gen-types", version = "0.112", default-features = false, optional = true } buddy-alloc = { version = "0.5.0", optional = true } ckb-x64-simulator = { version = "0.8", optional = true } +gcd = "2.3.0" [workspace] exclude = ["test"] diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 3766e1e..fb61772 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -51,11 +51,12 @@ dependencies = [ [[package]] name = "ckb-std" -version = "0.14.3" +version = "0.15.0" dependencies = [ "buddy-alloc", "cc", "ckb-gen-types", + "gcd", ] [[package]] @@ -87,6 +88,12 @@ dependencies = [ "ckb-std", ] +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "libc" version = "0.2.151" diff --git a/contracts/ckb-std-tests/src/entry.rs b/contracts/ckb-std-tests/src/entry.rs index 2d92fa1..ee74a68 100644 --- a/contracts/ckb-std-tests/src/entry.rs +++ b/contracts/ckb-std-tests/src/entry.rs @@ -10,6 +10,7 @@ use core::mem::size_of; #[cfg(target_arch = "riscv64")] use crate::code_hashes::CODE_HASH_SHARED_LIB; +use ckb_std::since::{EpochNumberWithFraction, Since}; #[cfg(target_arch = "riscv64")] use ckb_std::{dynamic_loading, dynamic_loading_c_impl}; #[cfg(target_arch = "riscv64")] @@ -266,6 +267,141 @@ fn test_current_cycles() { assert!(cycles > 300); } +fn test_since() { + assert_eq!( + Since::from_block_number(0x12300, true), + Some(Since::new(0x12300)) + ); + assert_eq!( + Since::from_block_number(0x12300, false), + Some(Since::new(0x8000_0000_0001_2300)) + ); + assert_eq!(Since::from_block_number(0x1100_0000_0000_2300, true), None); + assert_eq!( + Since::from_timestamp(0xffaa_1122, true), + Some(Since::new(0x4000_0000_ffaa_1122)) + ); + assert_eq!( + Since::from_timestamp(0xffaa_1122, false), + Some(Since::new(0xc000_0000_ffaa_1122)) + ); + assert_eq!(Since::from_timestamp(0x0100_0000_ffaa_1122, false), None); + assert_eq!( + Since::from_epoch(EpochNumberWithFraction::from_full_value(1), true), + Since::new(0x2000_0100_0000_0001) + ); + assert_eq!( + Since::from_epoch(EpochNumberWithFraction::from_full_value(1), false), + Since::new(0xa000_0100_0000_0001) + ); + assert_eq!(EpochNumberWithFraction::create(16777216, 1, 1000), None,); + assert_eq!(EpochNumberWithFraction::create(10000, 0, 0), None,); + assert_eq!(EpochNumberWithFraction::create(10000, 0, 65536), None,); + assert_eq!(EpochNumberWithFraction::create(10000, 65536, 65535), None,); + assert_eq!(EpochNumberWithFraction::create(10000, 1000, 1000), None,); + assert_eq!(EpochNumberWithFraction::create(10000, 1001, 1000), None,); + assert_eq!( + EpochNumberWithFraction::create(16777215, 65534, 65535) + .unwrap() + .full_value(), + 0xFF_FFFF_FEFF_FFFF, + ); + assert_eq!( + EpochNumberWithFraction::create(1000, 1, 7).unwrap() + + EpochNumberWithFraction::create(2000, 1, 5).unwrap(), + Some(EpochNumberWithFraction::create(3000, 12, 35).unwrap()), + ); + assert_eq!( + EpochNumberWithFraction::create(100, 7, 13).unwrap() + + EpochNumberWithFraction::create(50, 3, 5).unwrap(), + Some(EpochNumberWithFraction::create(151, 9, 65).unwrap()), + ); + assert_eq!( + EpochNumberWithFraction::create(30, 3, 8).unwrap() + + EpochNumberWithFraction::create(500, 5, 6).unwrap(), + Some(EpochNumberWithFraction::create(531, 5, 24).unwrap()), + ); + assert_eq!( + EpochNumberWithFraction::create(1000, 1, 1001).unwrap() + + EpochNumberWithFraction::create(2000, 7, 1003).unwrap(), + None, + ); + + assert!( + Since::from_block_number(1234, true).unwrap() + < Since::from_block_number(2000, true).unwrap(), + ); + assert!( + Since::from_block_number(2001, false).unwrap() + > Since::from_block_number(2000, false).unwrap(), + ); + assert!( + Since::from_timestamp(3111, true).unwrap() > Since::from_timestamp(2000, true).unwrap(), + ); + assert!( + Since::from_timestamp(1999, false).unwrap() < Since::from_timestamp(2000, false).unwrap(), + ); + assert!( + Since::from_epoch( + EpochNumberWithFraction::create(100, 999, 1000).unwrap(), + true + ) < Since::from_epoch(EpochNumberWithFraction::create(101, 1, 1000).unwrap(), true), + ); + assert!( + Since::from_epoch( + EpochNumberWithFraction::create(100, 600, 1000).unwrap(), + true + ) < Since::from_epoch(EpochNumberWithFraction::create(100, 8, 10).unwrap(), true), + ); + assert_eq!( + Since::from_block_number(1234, true) + .unwrap() + .partial_cmp(&Since::from_block_number(2000, false).unwrap()), + None, + ); + assert_eq!( + Since::from_epoch( + EpochNumberWithFraction::create(100, 999, 1000).unwrap(), + false + ) + .partial_cmp(&Since::from_epoch( + EpochNumberWithFraction::create(101, 1, 1000).unwrap(), + true + )), + None, + ); + assert_eq!( + Since::from_timestamp(1234, true) + .unwrap() + .partial_cmp(&Since::from_timestamp(2000, false).unwrap()), + None, + ); + assert_eq!( + Since::from_block_number(1234, true) + .unwrap() + .partial_cmp(&Since::from_timestamp(2000, true).unwrap()), + None, + ); + assert_eq!( + Since::from_block_number(1234, true) + .unwrap() + .partial_cmp(&Since::from_epoch( + EpochNumberWithFraction::create(101, 1, 1000).unwrap(), + true + )), + None, + ); + assert_eq!( + Since::from_timestamp(1234, true) + .unwrap() + .partial_cmp(&Since::from_epoch( + EpochNumberWithFraction::create(101, 1, 1000).unwrap(), + true + )), + None, + ); +} + pub fn main() -> Result<(), Error> { test_basic(); test_load_data(); @@ -287,5 +423,6 @@ pub fn main() -> Result<(), Error> { } test_vm_version(); test_current_cycles(); + test_since(); Ok(()) } diff --git a/src/since.rs b/src/since.rs index 76cf074..6789598 100644 --- a/src/since.rs +++ b/src/since.rs @@ -1,3 +1,5 @@ +use core::cmp::Ordering; + /// Transaction input's since field #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Since(u64); @@ -12,6 +14,37 @@ impl Since { const LOCK_BY_EPOCH_MASK: u64 = 0x2000_0000_0000_0000; const LOCK_BY_TIMESTAMP_MASK: u64 = 0x4000_0000_0000_0000; + pub fn from_block_number(number: u64, absolute: bool) -> Option { + if number & Self::FLAGS_MASK != 0 { + return None; + } + Some(Self::new( + number + | Self::LOCK_BY_BLOCK_NUMBER_MASK + | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }), + )) + } + + pub fn from_timestamp(timestamp: u64, absolute: bool) -> Option { + if timestamp & Self::FLAGS_MASK != 0 { + return None; + } + Some(Self::new( + timestamp + | Self::LOCK_BY_TIMESTAMP_MASK + | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }), + )) + } + + pub fn from_epoch(epoch: EpochNumberWithFraction, absolute: bool) -> Self { + debug_assert!(epoch.full_value() & Self::FLAGS_MASK == 0); + Self::new( + epoch.full_value() + | Self::LOCK_BY_EPOCH_MASK + | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }), + ) + } + pub fn new(v: u64) -> Self { Since(v) } @@ -52,6 +85,56 @@ impl Since { _ => None, } } + + /// Given the base commitment block, this method converts a relative since + /// value to an absolute since value for later comparison. + #[cfg(feature = "ckb-types")] + pub fn to_absolute_value(self, base_block: ckb_types::packed::Header) -> Option { + debug_assert!(self.is_relative()); + + let to_le_u64 = |v: &ckb_types::packed::Uint64| { + let mut tmp = [0u8; 8]; + tmp.copy_from_slice(&v.raw_data()); + u64::from_le_bytes(tmp) + }; + + match self.extract_lock_value() { + Some(LockValue::BlockNumber(number)) => to_le_u64(&base_block.raw().number()) + .checked_add(number) + .and_then(|block_number| Self::from_block_number(block_number, true)), + Some(LockValue::Timestamp(timestamp)) => to_le_u64(&base_block.raw().timestamp()) + .checked_add(timestamp) + .and_then(|timestamp| Self::from_timestamp(timestamp, true)), + Some(LockValue::EpochNumberWithFraction(epoch)) => { + let base_epoch = EpochNumberWithFraction::from_full_value( + to_le_u64(&base_block.raw().epoch()) & Self::VALUE_MASK, + ); + Some(Self::from_epoch((epoch + base_epoch)?, true)) + } + _ => None, + } + } +} + +impl PartialOrd for Since { + fn partial_cmp(&self, other: &Since) -> Option { + // Given 2 since values alone, there is no way to compare an absolute value + // to a relative value. However, a higher level method can convert a relative + // value to an absolute value. + if self.is_absolute() != other.is_absolute() { + return None; + } + + match (self.extract_lock_value(), other.extract_lock_value()) { + (Some(LockValue::BlockNumber(a)), Some(LockValue::BlockNumber(b))) => a.partial_cmp(&b), + ( + Some(LockValue::EpochNumberWithFraction(a)), + Some(LockValue::EpochNumberWithFraction(b)), + ) => a.partial_cmp(&b), + (Some(LockValue::Timestamp(a)), Some(LockValue::Timestamp(b))) => a.partial_cmp(&b), + _ => None, + } + } } pub enum LockValue { @@ -103,11 +186,25 @@ impl EpochNumberWithFraction { pub const LENGTH_MAXIMUM_VALUE: u64 = (1u64 << Self::LENGTH_BITS); pub const LENGTH_MASK: u64 = (Self::LENGTH_MAXIMUM_VALUE - 1); + pub fn create(number: u64, index: u64, length: u64) -> Option { + if number < Self::NUMBER_MAXIMUM_VALUE + && index < Self::INDEX_MAXIMUM_VALUE + && length < Self::LENGTH_MAXIMUM_VALUE + && length > 0 + && index < length + { + Some(Self::new_unchecked(number, index, length)) + } else { + None + } + } + pub fn new(number: u64, index: u64, length: u64) -> EpochNumberWithFraction { debug_assert!(number < Self::NUMBER_MAXIMUM_VALUE); debug_assert!(index < Self::INDEX_MAXIMUM_VALUE); debug_assert!(length < Self::LENGTH_MAXIMUM_VALUE); debug_assert!(length > 0); + debug_assert!(index < length); Self::new_unchecked(number, index, length) } @@ -149,3 +246,43 @@ impl EpochNumberWithFraction { } } } + +impl PartialOrd for EpochNumberWithFraction { + fn partial_cmp(&self, other: &EpochNumberWithFraction) -> Option { + if self.number() < other.number() { + Some(Ordering::Less) + } else if self.number() > other.number() { + Some(Ordering::Greater) + } else { + let block_a = (self.index() as u128) * (other.length() as u128); + let block_b = (other.index() as u128) * (self.length() as u128); + block_a.partial_cmp(&block_b) + } + } +} + +impl core::ops::Add for EpochNumberWithFraction { + type Output = Option; + + fn add(self, rhs: EpochNumberWithFraction) -> Self::Output { + let mut number = self.number().checked_add(rhs.number())?; + + let mut numerator = ((self.index() as u128) * (rhs.length() as u128)) + .checked_add((rhs.index() as u128) * (self.length() as u128))?; + let mut denominator = (self.length() as u128) * (rhs.length() as u128); + let divisor = gcd::binary_u128(numerator, denominator); + debug_assert!(numerator % divisor == 0); + debug_assert!(denominator % divisor == 0); + numerator /= divisor; + denominator /= divisor; + + let full_epoches = u64::try_from(numerator / denominator).ok()?; + number = number.checked_add(full_epoches)?; + numerator %= denominator; + + let numerator = u64::try_from(numerator).ok()?; + let denominator = u64::try_from(denominator).ok()?; + + EpochNumberWithFraction::create(number, numerator, denominator) + } +}