Skip to content

Commit

Permalink
[Move] Sui Parsers for Module ID and SuiAddress (MystenLabs#15625)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
amnn committed Jan 12, 2024
1 parent 66fbbc7 commit 985b8ca
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 24 deletions.
59 changes: 58 additions & 1 deletion crates/sui-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SuiAddress> {
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<ModuleId> {
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<StructTag> {
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<TypeTag> {
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<AccountAddress> {
match addr {
"deepbook" => Some(DEEPBOOK_ADDRESS),
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<vector<0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>>>")
let result = parse_sui_struct_tag("0x00000000000000000000000000000000000000000000000000000000000000e7::vec_coin::VecCoin<vector<0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>>>")
.expect("should not error");

let expected = expect!["0xe7::vec_coin::VecCoin<vector<0x2::coin::Coin<0x2::sui::SUI>>>"];
Expand Down
54 changes: 37 additions & 17 deletions external-crates/move/crates/move-command-line-common/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -41,6 +41,12 @@ impl ParsedType {
}
}

impl ParsedModuleId {
pub fn parse(s: &str) -> Result<ParsedModuleId> {
parse(s, |parser| parser.parse_module_id())
}
}

impl ParsedStructType {
pub fn parse(s: &str) -> Result<ParsedStructType> {
let ty = parse(s, |parser| parser.parse_type())
Expand Down Expand Up @@ -136,18 +142,39 @@ impl<'a, Tok: Token, I: Iterator<Item = (Tok, &'a str)>> Parser<'a, Tok, I> {
}

impl<'a, I: Iterator<Item = (TypeToken, &'a str)>> Parser<'a, TypeToken, I> {
pub fn parse_module_id(&mut self) -> Result<ParsedModuleId> {
let (tok, contents) = self.advance_any()?;
self.parse_module_id_impl(tok, contents)
}

pub fn parse_type(&mut self) -> Result<ParsedType> {
self.parse_type_impl(0)
}

pub fn parse_module_id_impl(
&mut self,
tok: TypeToken,
contents: &'a str,
) -> Result<ParsedModuleId> {
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<ParsedType> {
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,
Expand All @@ -163,17 +190,11 @@ impl<'a, I: Iterator<Item = (TypeToken, &'a str)>> 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)?;
Expand All @@ -189,13 +210,12 @@ impl<'a, I: Iterator<Item = (TypeToken, &'a str)>> 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"),
})
}
}
Expand Down
28 changes: 22 additions & 6 deletions external-crates/move/crates/move-command-line-common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<ParsedType>,
}
Expand Down Expand Up @@ -116,20 +121,31 @@ impl Token for TypeToken {
}
}

impl ParsedModuleId {
pub fn into_module_id(
self,
mapping: &impl Fn(&str) -> Option<AccountAddress>,
) -> anyhow::Result<ModuleId> {
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<AccountAddress>,
) -> anyhow::Result<StructTag> {
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 + '_ {
Expand Down

0 comments on commit 985b8ca

Please sign in to comment.