From 985b8ca75cbd8077a5aea75539d3f486ca29cec4 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Tue, 9 Jan 2024 15:52:17 +0000 Subject: [PATCH] [Move] Sui Parsers for Module ID and SuiAddress (#15625) Description: Expose parsers for `SuiAddress` and `ModuleId` based on the `move-command-line-common` parser framework, for (later) use in GraphQL. Test Plan: Added new unit tests for these parsers: ``` sui-types$ cargo nextest run ``` --- crates/sui-types/src/lib.rs | 59 ++++++++++++++++++- .../move-command-line-common/src/parser.rs | 54 +++++++++++------ .../move-command-line-common/src/types.rs | 28 +++++++-- .../move-core-types/src/language_storage.rs | 4 ++ 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/crates/sui-types/src/lib.rs b/crates/sui-types/src/lib.rs index 597f0c2a4e3f0..fa53fa44aeb18 100644 --- a/crates/sui-types/src/lib.rs +++ b/crates/sui-types/src/lib.rs @@ -11,6 +11,7 @@ use base_types::{SequenceNumber, SuiAddress}; use move_binary_format::binary_views::BinaryIndexedView; use move_binary_format::file_format::{AbilitySet, SignatureToken}; use move_bytecode_utils::resolve_struct; +use move_core_types::language_storage::ModuleId; use move_core_types::{account_address::AccountAddress, language_storage::StructTag}; pub use move_core_types::{identifier::Identifier, language_storage::TypeTag}; use object::OBJECT_START_VERSION; @@ -149,16 +150,46 @@ pub fn sui_framework_address_concat_string(suffix: &str) -> String { format!("{}{suffix}", SUI_FRAMEWORK_ADDRESS.to_hex_literal()) } +/// Parses `s` as an address. Valid formats for addresses are: +/// +/// - A 256bit number, encoded in decimal, or hexadecimal with a leading "0x" prefix. +/// - One of a number of pre-defined named addresses: std, sui, sui_system, deepbook. +/// +/// Parsing succeeds if and only if `s` matches one of these formats exactly, with no remaining +/// suffix. This function is intended for use within the authority codebases. +pub fn parse_sui_address(s: &str) -> anyhow::Result { + use move_command_line_common::address::ParsedAddress; + Ok(ParsedAddress::parse(s)? + .into_account_address(&resolve_address)? + .into()) +} + +/// Parse `s` as a Module ID: An address (see `parse_sui_address`), followed by `::`, and then a +/// module name (an identifier). Parsing succeeds if and only if `s` matches this format exactly, +/// with no remaining input. This function is intended for use within the authority codebases. +pub fn parse_sui_module_id(s: &str) -> anyhow::Result { + use move_command_line_common::types::ParsedModuleId; + ParsedModuleId::parse(s)?.into_module_id(&resolve_address) +} + +/// Parse `s` as a struct type: A fully-qualified name, optionally followed by a list of type +/// parameters (types -- see `parse_sui_type_tag`, separated by commas, surrounded by angle +/// brackets). Parsing succeeds if and only if `s` matches this format exactly, with no remaining +/// input. This function is intended for use within the authority codebase. pub fn parse_sui_struct_tag(s: &str) -> anyhow::Result { use move_command_line_common::types::ParsedStructType; ParsedStructType::parse(s)?.into_struct_tag(&resolve_address) } +/// Parse `s` as a type: Either a struct type (see `parse_sui_struct_tag`), a primitive type, or a +/// vector with a type parameter. Parsing succeeds if and only if `s` matches this format exactly, +/// with no remaining input. This function is intended for use within the authority codebase. pub fn parse_sui_type_tag(s: &str) -> anyhow::Result { use move_command_line_common::types::ParsedType; ParsedType::parse(s)?.into_type_tag(&resolve_address) } +/// Resolve well-known named addresses into numeric addresses. fn resolve_address(addr: &str) -> Option { match addr { "deepbook" => Some(DEEPBOOK_ADDRESS), @@ -282,6 +313,32 @@ mod tests { use super::*; use expect_test::expect; + #[test] + fn test_parse_sui_numeric_address() { + let result = parse_sui_address("0x2").expect("should not error"); + + let expected = + expect!["0x0000000000000000000000000000000000000000000000000000000000000002"]; + expected.assert_eq(&result.to_string()); + } + + #[test] + fn test_parse_sui_named_address() { + let result = parse_sui_address("sui").expect("should not error"); + + let expected = + expect!["0x0000000000000000000000000000000000000000000000000000000000000002"]; + expected.assert_eq(&result.to_string()); + } + + #[test] + fn test_parse_sui_module_id() { + let result = parse_sui_module_id("0x2::sui").expect("should not error"); + let expected = + expect!["0x0000000000000000000000000000000000000000000000000000000000000002::sui"]; + expected.assert_eq(&result.to_canonical_string(/* with_prefix */ true)); + } + #[test] fn test_parse_sui_struct_tag_short_account_addr() { let result = parse_sui_struct_tag("0x2::sui::SUI").expect("should not error"); @@ -348,7 +405,7 @@ mod tests { #[test] fn test_complex_struct_tag_with_long_addr() { - let result = parse_sui_struct_tag("0x00000000000000000000000000000000000000000000000000000000000000e7::vec_coin::VecCoin>>") + let result = parse_sui_struct_tag("0x00000000000000000000000000000000000000000000000000000000000000e7::vec_coin::VecCoin>>") .expect("should not error"); let expected = expect!["0xe7::vec_coin::VecCoin>>"]; diff --git a/external-crates/move/crates/move-command-line-common/src/parser.rs b/external-crates/move/crates/move-command-line-common/src/parser.rs index e6856c0ff3d17..da68bde661eb4 100644 --- a/external-crates/move/crates/move-command-line-common/src/parser.rs +++ b/external-crates/move/crates/move-command-line-common/src/parser.rs @@ -3,7 +3,7 @@ use crate::{ address::{NumericalAddress, ParsedAddress}, - types::{ParsedStructType, ParsedType, TypeToken}, + types::{ParsedModuleId, ParsedStructType, ParsedType, TypeToken}, values::{ParsableValue, ParsedValue, ValueToken}, }; use anyhow::{anyhow, bail, Result}; @@ -41,6 +41,12 @@ impl ParsedType { } } +impl ParsedModuleId { + pub fn parse(s: &str) -> Result { + parse(s, |parser| parser.parse_module_id()) + } +} + impl ParsedStructType { pub fn parse(s: &str) -> Result { let ty = parse(s, |parser| parser.parse_type()) @@ -136,18 +142,39 @@ impl<'a, Tok: Token, I: Iterator> Parser<'a, Tok, I> { } impl<'a, I: Iterator> Parser<'a, TypeToken, I> { + pub fn parse_module_id(&mut self) -> Result { + let (tok, contents) = self.advance_any()?; + self.parse_module_id_impl(tok, contents) + } + pub fn parse_type(&mut self) -> Result { self.parse_type_impl(0) } + pub fn parse_module_id_impl( + &mut self, + tok: TypeToken, + contents: &'a str, + ) -> Result { + let tok = match tok { + TypeToken::Ident => ValueToken::Ident, + TypeToken::AddressIdent => ValueToken::Number, + tok => bail!("unexpected token {tok}, expected address"), + }; + let address = parse_address_impl(tok, contents)?; + self.advance(TypeToken::ColonColon)?; + let name = self.advance(TypeToken::Ident)?.to_owned(); + Ok(ParsedModuleId { address, name }) + } + fn parse_type_impl(&mut self, depth: u64) -> Result { self.count += 1; if depth > MAX_TYPE_DEPTH || self.count > MAX_TYPE_NODE_COUNT { bail!("Type exceeds maximum nesting depth or node count") } - let (tok, contents) = self.advance_any()?; - Ok(match (tok, contents) { + + Ok(match self.advance_any()? { (TypeToken::Ident, "u8") => ParsedType::U8, (TypeToken::Ident, "u16") => ParsedType::U16, (TypeToken::Ident, "u32") => ParsedType::U32, @@ -163,17 +190,11 @@ impl<'a, I: Iterator> Parser<'a, TypeToken, I> { self.advance(TypeToken::Gt)?; ParsedType::Vector(Box::new(ty)) } - (TypeToken::Ident, _) | (TypeToken::AddressIdent, _) => { - let addr_tok = match tok { - TypeToken::Ident => ValueToken::Ident, - TypeToken::AddressIdent => ValueToken::Number, - _ => unreachable!(), - }; - let address = parse_address_impl(addr_tok, contents)?; - self.advance(TypeToken::ColonColon)?; - let module_contents = self.advance(TypeToken::Ident)?; + + (tok @ (TypeToken::Ident | TypeToken::AddressIdent), contents) => { + let module = self.parse_module_id_impl(tok, contents)?; self.advance(TypeToken::ColonColon)?; - let struct_contents = self.advance(TypeToken::Ident)?; + let name = self.advance(TypeToken::Ident)?.to_owned(); let type_args = match self.peek_tok() { Some(TypeToken::Lt) => { self.advance(TypeToken::Lt)?; @@ -189,13 +210,12 @@ impl<'a, I: Iterator> Parser<'a, TypeToken, I> { _ => vec![], }; ParsedType::Struct(ParsedStructType { - address, - module: module_contents.to_owned(), - name: struct_contents.to_owned(), + module, + name, type_args, }) } - _ => bail!("unexpected token {}, expected type", tok), + (tok, _) => bail!("unexpected token {tok}, expected type"), }) } } diff --git a/external-crates/move/crates/move-command-line-common/src/types.rs b/external-crates/move/crates/move-command-line-common/src/types.rs index 64feac149ce1c..d60126017e38c 100644 --- a/external-crates/move/crates/move-command-line-common/src/types.rs +++ b/external-crates/move/crates/move-command-line-common/src/types.rs @@ -7,7 +7,7 @@ use anyhow::bail; use move_core_types::{ account_address::AccountAddress, identifier::{self, Identifier}, - language_storage::{StructTag, TypeTag}, + language_storage::{ModuleId, StructTag, TypeTag}, }; use crate::{address::ParsedAddress, parser::Token}; @@ -24,9 +24,14 @@ pub enum TypeToken { } #[derive(Eq, PartialEq, Debug, Clone)] -pub struct ParsedStructType { +pub struct ParsedModuleId { pub address: ParsedAddress, - pub module: String, + pub name: String, +} + +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct ParsedStructType { + pub module: ParsedModuleId, pub name: String, pub type_args: Vec, } @@ -116,20 +121,31 @@ impl Token for TypeToken { } } +impl ParsedModuleId { + pub fn into_module_id( + self, + mapping: &impl Fn(&str) -> Option, + ) -> anyhow::Result { + Ok(ModuleId::new( + self.address.into_account_address(mapping)?, + Identifier::new(self.name)?, + )) + } +} + impl ParsedStructType { pub fn into_struct_tag( self, mapping: &impl Fn(&str) -> Option, ) -> anyhow::Result { let Self { - address, module, name, type_args, } = self; Ok(StructTag { - address: address.into_account_address(mapping)?, - module: Identifier::new(module)?, + address: module.address.into_account_address(mapping)?, + module: Identifier::new(module.name)?, name: Identifier::new(name)?, type_params: type_args .into_iter() diff --git a/external-crates/move/crates/move-core-types/src/language_storage.rs b/external-crates/move/crates/move-core-types/src/language_storage.rs index 5ba9cc0eceb57..834c3277567bc 100644 --- a/external-crates/move/crates/move-core-types/src/language_storage.rs +++ b/external-crates/move/crates/move-core-types/src/language_storage.rs @@ -308,6 +308,10 @@ impl ModuleId { key } + pub fn to_canonical_string(&self, with_prefix: bool) -> String { + self.to_canonical_display(with_prefix).to_string() + } + /// Proxy type for overriding `ModuleId`'s display implementation, to use a canonical form /// (full-width addresses), with an optional "0x" prefix (controlled by the `with_prefix` flag). pub fn to_canonical_display(&self, with_prefix: bool) -> impl Display + '_ {