Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions circuit/program/src/data/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum Access<A: Aleo> {
Member(Identifier<A>),
/// Access an element of an array.
Index(U32<A>),
/// Access a contiguous sub-array, i.e. the half-open range `[start, end)`.
Range(U32<A>, U32<A>),
}

impl<A: Aleo> Inject for Access<A> {
Expand All @@ -41,6 +43,7 @@ impl<A: Aleo> Inject for Access<A> {
match plaintext {
Self::Primitive::Member(identifier) => Self::Member(Identifier::constant(identifier)),
Self::Primitive::Index(index) => Self::Index(U32::new(_m, index)),
Self::Primitive::Range(start, end) => Self::Range(U32::new(_m, start), U32::new(_m, end)),
}
}
}
Expand All @@ -53,6 +56,7 @@ impl<A: Aleo> Eject for Access<A> {
match self {
Self::Member(member) => member.eject_mode(),
Self::Index(index) => index.eject_mode(),
Self::Range(start, end) => Mode::combine(start.eject_mode(), [end.eject_mode()]),
}
}

Expand All @@ -61,6 +65,7 @@ impl<A: Aleo> Eject for Access<A> {
match self {
Self::Member(identifier) => console::Access::Member(identifier.eject_value()),
Self::Index(index) => console::Access::Index(index.eject_value()),
Self::Range(start, end) => console::Access::Range(start.eject_value(), end.eject_value()),
}
}
}
Expand Down
86 changes: 51 additions & 35 deletions circuit/program/src/data/plaintext/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,53 +15,69 @@

use super::*;

use std::borrow::Cow;

impl<A: Aleo> Plaintext<A> {
/// Returns the plaintext member from the given path.
pub fn find<A0: Into<Access<A>> + Clone + Debug>(&self, path: &[A0]) -> Result<Plaintext<A>> {
// Ensure the path is not empty.
if path.is_empty() {
A::halt("Attempted to find member with an empty path.")
}
// Walk the path and return an owned copy of the located value.
self.find_cow(path).map(Cow::into_owned)
}

match self {
// Halts if the value is not a struct or an array.
Self::Literal(..) => A::halt("A literal is not a struct or an array"),
// Retrieve the value of the member (from the value).
Self::Struct(..) | Self::Array(..) => {
// Initialize the plaintext starting from the top-level.
let mut plaintext = self;
/// Walks the given path, returning the located value borrowed when possible and owned when a
/// range access requires constructing a new sub-array.
fn find_cow<A0: Into<Access<A>> + Clone + Debug>(&self, path: &[A0]) -> Result<Cow<'_, Plaintext<A>>> {
// If the path is exhausted, return the current value.
let Some((access, remaining)) = path.split_first() else {
return Ok(Cow::Borrowed(self));
};

// Iterate through the path to retrieve the value.
for access in path.iter() {
let access = access.clone().into();
match (plaintext, &access) {
(Self::Struct(members, ..), Access::Member(identifier)) => {
match members.get(identifier) {
// Retrieve the member and update `plaintext` for the next iteration.
Some(member) => plaintext = member,
// Halts if the member does not exist.
None => bail!("Failed to locate member '{identifier}'"),
}
}
(Self::Array(array, ..), Access::Index(index)) => {
let index = match index.eject_mode() {
Mode::Constant => index.eject_value(),
_ => bail!("'{index}' must be a constant"),
};
match array.get(*index as usize) {
// Retrieve the element and update `plaintext` for the next iteration.
Some(element) => plaintext = element,
// Halts if the element does not exist.
None => bail!("Failed to locate element '{index}'"),
}
}
_ => bail!("Invalid access `{access}``"),
match (self, access.clone().into()) {
(Self::Struct(members, ..), Access::Member(identifier)) => match members.get(&identifier) {
// Continue walking from the member.
Some(member) => member.find_cow(remaining),
// Halts if the member does not exist.
None => bail!("Failed to locate member '{identifier}'"),
},
(Self::Array(array, ..), Access::Index(index)) => {
// The index must be a constant, as array indices are resolved at synthesis time.
let index = match index.eject_mode() {
Mode::Constant => index.eject_value(),
_ => bail!("'{index}' must be a constant"),
};
match array.get(*index as usize) {
// Continue walking from the element.
Some(element) => element.find_cow(remaining),
// Halts if the element does not exist.
None => bail!("Failed to locate element '{index}'"),
}
}
(Self::Array(array, ..), Access::Range(start, end)) => {
// The bounds must be constants, as array ranges are resolved at synthesis time.
let start = match start.eject_mode() {
Mode::Constant => start.eject_value(),
_ => bail!("'{start}' must be a constant"),
};
let end = match end.eject_mode() {
Mode::Constant => end.eject_value(),
_ => bail!("'{end}' must be a constant"),
};
match array.get(*start as usize..*end as usize) {
// Construct the sub-array, then continue walking from it. As the sub-array is
// owned locally, the remaining walk must return an owned value.
Some(elements) => {
let sub_array = Self::Array(elements.to_vec(), Default::default());
Ok(Cow::Owned(sub_array.find_cow(remaining)?.into_owned()))
}
// Halts if the range is out of bounds.
None => bail!("Range '{start}..{end}' is out of bounds"),
}

// Return the output.
Ok(plaintext.clone())
}
_ => bail!("Invalid access `{}`", access.clone().into()),
}
}
}
1 change: 1 addition & 0 deletions circuit/program/src/data/record/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl<A: Aleo> Record<A, Plaintext<A>> {
let first = match first.clone().into() {
Access::Member(identifier) => identifier,
Access::Index(_) => bail!("Attempted to index into a record"),
Access::Range(..) => bail!("Attempted to slice a record"),
};
// Retrieve the top-level entry.
match self.data.get(&first) {
Expand Down
7 changes: 7 additions & 0 deletions console/network/src/consensus_heights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pub enum ConsensusVersion {
/// Increase the anchor time to 35.
/// Unconditionally stores transaction rejection reasons.
V15 = 15,
/// V16: Introduces array range access (`r[a..b]`) and array-flattening `cast`.
V16 = 16,
}

impl ToBytes for ConsensusVersion {
Expand Down Expand Up @@ -85,6 +87,7 @@ impl FromBytes for ConsensusVersion {
13 => Ok(Self::V13),
14 => Ok(Self::V14),
15 => Ok(Self::V15),
16 => Ok(Self::V16),
_ => Err(io_error("Invalid consensus version")),
}
}
Expand Down Expand Up @@ -123,6 +126,7 @@ pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CON
(ConsensusVersion::V13, 10_881_000),
(ConsensusVersion::V14, 11_960_000),
(ConsensusVersion::V15, u32::MAX),
(ConsensusVersion::V16, u32::MAX),
];

/// The consensus version height for `MainnetV0`.
Expand All @@ -142,6 +146,7 @@ pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CO
(ConsensusVersion::V13, 16_850_000),
(ConsensusVersion::V14, 17_700_000),
(ConsensusVersion::V15, 19_264_000),
(ConsensusVersion::V16, u32::MAX),
];

/// The consensus version heights for `TestnetV0`.
Expand All @@ -161,6 +166,7 @@ pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CO
(ConsensusVersion::V13, 14_906_000),
(ConsensusVersion::V14, 15_370_000),
(ConsensusVersion::V15, 16_886_000),
(ConsensusVersion::V16, u32::MAX),
];

/// The consensus version heights when the `test_consensus_heights` feature is enabled.
Expand All @@ -180,6 +186,7 @@ pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSU
(ConsensusVersion::V13, 16),
(ConsensusVersion::V14, 17),
(ConsensusVersion::V15, 18),
(ConsensusVersion::V16, 19),
];

#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
Expand Down
13 changes: 12 additions & 1 deletion console/program/src/data/access/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ impl<N: Network> FromBytes for Access<N> {
match variant {
0 => Ok(Self::Member(Identifier::read_le(&mut reader)?)),
1 => Ok(Self::Index(U32::read_le(&mut reader)?)),
2.. => Err(error(format!("Failed to deserialize access variant {variant}"))),
2 => Ok(Self::Range(U32::read_le(&mut reader)?, U32::read_le(&mut reader)?)),
3.. => Err(error(format!("Failed to deserialize access variant {variant}"))),
}
}
}
Expand All @@ -39,6 +40,11 @@ impl<N: Network> ToBytes for Access<N> {
1u8.write_le(&mut writer)?;
index.write_le(&mut writer)
}
Access::Range(start, end) => {
2u8.write_le(&mut writer)?;
start.write_le(&mut writer)?;
end.write_le(&mut writer)
}
}
}
}
Expand Down Expand Up @@ -72,6 +78,11 @@ mod tests {
// Index
let index = U32::<CurrentNetwork>::rand(rng);
check_bytes(Access::Index(index))?;

// Range
let start = U32::<CurrentNetwork>::rand(rng);
let end = U32::<CurrentNetwork>::rand(rng);
check_bytes(Access::Range(start, end))?;
}
Ok(())
}
Expand Down
10 changes: 10 additions & 0 deletions console/program/src/data/access/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub enum Access<N: Network> {
Member(Identifier<N>),
/// Access an element of an array.
Index(U32<N>),
/// Access a contiguous sub-array, i.e. the half-open range `[start, end)`.
Range(U32<N>, U32<N>),
}

impl<N: Network> From<Identifier<N>> for Access<N> {
Expand All @@ -44,3 +46,11 @@ impl<N: Network> From<U32<N>> for Access<N> {
Self::Index(index)
}
}

impl<N: Network> From<core::ops::Range<U32<N>>> for Access<N> {
/// Initializes a new range access from a `start..end` range.
#[inline]
fn from(range: core::ops::Range<U32<N>>) -> Self {
Self::Range(range.start, range.end)
}
}
19 changes: 15 additions & 4 deletions console/program/src/data/access/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ use super::*;

impl<N: Network> Parser for Access<N> {
fn parse(string: &str) -> ParserResult<Self> {
alt((
map(pair(tag("["), pair(U32::parse, tag("]"))), |(_, (index, _))| Self::Index(index)),
map(pair(tag("."), Identifier::parse), |(_, identifier)| Self::Member(identifier)),
))(string)
// Parses a range access, i.e. `[start..end]`.
let parse_range = map(
pair(tag("["), pair(U32::parse, pair(tag(".."), pair(U32::parse, tag("]"))))),
|(_, (start, (_, (end, _))))| Self::Range(start, end),
);
// Parses an index access, i.e. `[index]`.
let parse_index = map(pair(tag("["), pair(U32::parse, tag("]"))), |(_, (index, _))| Self::Index(index));
// Parses a member access, i.e. `.member`.
let parse_member = map(pair(tag("."), Identifier::parse), |(_, identifier)| Self::Member(identifier));
// Attempt to parse a range before an index, as both begin with `[`.
alt((parse_range, parse_index, parse_member))(string)
}
}

Expand Down Expand Up @@ -57,6 +64,8 @@ impl<N: Network> Display for Access<N> {
Self::Member(identifier) => write!(f, ".{identifier}"),
// Prints the access index, i.e. `[0u32]`
Self::Index(index) => write!(f, "[{index}]"),
// Prints the access range, i.e. `[0u32..4u32]`
Self::Range(start, end) => write!(f, "[{start}..{end}]"),
}
}
}
Expand All @@ -72,6 +81,7 @@ mod tests {
fn test_parse() -> Result<()> {
assert_eq!(Access::parse(".data"), Ok(("", Access::<CurrentNetwork>::Member(Identifier::from_str("data")?))));
assert_eq!(Access::parse("[0u32]"), Ok(("", Access::<CurrentNetwork>::Index(U32::new(0)))));
assert_eq!(Access::parse("[0u32..4u32]"), Ok(("", Access::<CurrentNetwork>::Range(U32::new(0), U32::new(4)))));
Ok(())
}

Expand Down Expand Up @@ -100,6 +110,7 @@ mod tests {
fn test_display() -> Result<()> {
assert_eq!(Access::<CurrentNetwork>::Member(Identifier::from_str("foo")?).to_string(), ".foo");
assert_eq!(Access::<CurrentNetwork>::Index(U32::new(0)).to_string(), "[0u32]");
assert_eq!(Access::<CurrentNetwork>::Range(U32::new(0), U32::new(4)).to_string(), "[0u32..4u32]");
Ok(())
}
}
2 changes: 2 additions & 0 deletions console/program/src/data/access/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ mod tests {
for i in 0..1000 {
check_serde_json(Access::<CurrentNetwork>::from_str(&format!(".owner_{i}")).unwrap());
check_serde_json(Access::<CurrentNetwork>::from_str(&format!("[{i}u32]")).unwrap());
check_serde_json(Access::<CurrentNetwork>::from_str(&format!("[{i}u32..{}u32]", i + 1)).unwrap());
}
}

Expand All @@ -85,6 +86,7 @@ mod tests {
for i in 0..1000 {
check_bincode(Access::<CurrentNetwork>::from_str(&format!(".owner_{i}")).unwrap());
check_bincode(Access::<CurrentNetwork>::from_str(&format!("[{i}u32]")).unwrap());
check_bincode(Access::<CurrentNetwork>::from_str(&format!("[{i}u32..{}u32]", i + 1)).unwrap());
}
}
}
Loading