Skip to content

Commit

Permalink
[cli][ptb] Fix type resolution for primitive type instantiations in P…
Browse files Browse the repository at this point in the history
…TB CLI (#20078)

## Description 

Fixes an issue where type layouts were not always returned for all
primitive types, and simplifies the APIs around this a bit.

## Test plan 

Added a repro test and made sure it passed.

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [X] CLI: Bugfix to allow resolving generic type parameters
instantiated with primitive types in the CLI PTB builder.
- [ ] Rust SDK: 
- [ ] REST API:
  • Loading branch information
tzakian authored Nov 1, 2024
1 parent 3bd2018 commit 69d3653
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 113 deletions.
2 changes: 1 addition & 1 deletion crates/sui-json-rpc-types/src/sui_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1871,7 +1871,7 @@ fn get_signature_types(
.signature_at(func.parameters)
.0
.iter()
.map(|s| primitive_type(module, &[], s).1)
.map(|s| primitive_type(module, &[], s))
.collect(),
)
} else {
Expand Down
192 changes: 83 additions & 109 deletions crates/sui-json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ use fastcrypto::encoding::{Encoding, Hex};
use move_binary_format::CompiledModule;
use move_binary_format::{binary_config::BinaryConfig, file_format::SignatureToken};
use move_bytecode_utils::resolve_struct;
use move_core_types::account_address::AccountAddress;
pub use move_core_types::annotated_value::MoveTypeLayout;
use move_core_types::annotated_value::{MoveFieldLayout, MoveVariant};
use move_core_types::identifier::IdentStr;
use move_core_types::u256::U256;
use move_core_types::{
annotated_value::{MoveStruct, MoveStructLayout, MoveValue},
ident_str,
annotated_value::{MoveStruct, MoveValue},
identifier::Identifier,
language_storage::{StructTag, TypeTag},
runtime_value as R,
Expand All @@ -27,11 +24,12 @@ use serde::{Deserialize, Serialize};
use serde_json::{json, Number, Value as JsonValue};

use sui_types::base_types::{
is_primitive_type_tag, ObjectID, SuiAddress, TxContext, TxContextKind, RESOLVED_ASCII_STR,
RESOLVED_STD_OPTION, RESOLVED_UTF8_STR, STD_ASCII_MODULE_NAME, STD_ASCII_STRUCT_NAME,
STD_OPTION_MODULE_NAME, STD_OPTION_STRUCT_NAME, STD_UTF8_MODULE_NAME, STD_UTF8_STRUCT_NAME,
is_primitive_type_tag, move_ascii_str_layout, move_utf8_str_layout, ObjectID, SuiAddress,
TxContext, TxContextKind, RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR,
STD_ASCII_MODULE_NAME, STD_ASCII_STRUCT_NAME, STD_OPTION_MODULE_NAME, STD_OPTION_STRUCT_NAME,
STD_UTF8_MODULE_NAME, STD_UTF8_STRUCT_NAME,
};
use sui_types::id::{ID, RESOLVED_SUI_ID};
use sui_types::id::{self, ID, RESOLVED_SUI_ID};
use sui_types::move_package::MovePackage;
use sui_types::object::bounded_visitor::BoundedVisitor;
use sui_types::transfer::RESOLVED_RECEIVING_STRUCT;
Expand Down Expand Up @@ -566,103 +564,96 @@ pub fn primitive_type(
view: &CompiledModule,
type_args: &[TypeTag],
param: &SignatureToken,
) -> (bool, Option<MoveTypeLayout>) {
match param {
SignatureToken::Bool => (true, Some(MoveTypeLayout::Bool)),
SignatureToken::U8 => (true, Some(MoveTypeLayout::U8)),
SignatureToken::U16 => (true, Some(MoveTypeLayout::U16)),
SignatureToken::U32 => (true, Some(MoveTypeLayout::U32)),
SignatureToken::U64 => (true, Some(MoveTypeLayout::U64)),
SignatureToken::U128 => (true, Some(MoveTypeLayout::U128)),
SignatureToken::U256 => (true, Some(MoveTypeLayout::U256)),
SignatureToken::Address => (true, Some(MoveTypeLayout::Address)),
) -> Option<MoveTypeLayout> {
Some(match param {
SignatureToken::Bool => MoveTypeLayout::Bool,
SignatureToken::U8 => MoveTypeLayout::U8,
SignatureToken::U16 => MoveTypeLayout::U16,
SignatureToken::U32 => MoveTypeLayout::U32,
SignatureToken::U64 => MoveTypeLayout::U64,
SignatureToken::U128 => MoveTypeLayout::U128,
SignatureToken::U256 => MoveTypeLayout::U256,
SignatureToken::Address => MoveTypeLayout::Address,
SignatureToken::Vector(inner) => {
let (is_primitive, inner_layout_opt) = primitive_type(view, type_args, inner);
match inner_layout_opt {
Some(inner_layout) => (
is_primitive,
Some(MoveTypeLayout::Vector(Box::new(inner_layout))),
),
None => (is_primitive, None),
}
MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, inner)?))
}
SignatureToken::Datatype(struct_handle_idx) => {
let resolved_struct = resolve_struct(view, *struct_handle_idx);
if resolved_struct == RESOLVED_ASCII_STR {
(
true,
Some(MoveTypeLayout::Struct(Box::new(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_ASCII_STR),
fields: Box::new(vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
)]),
}))),
)
MoveTypeLayout::Struct(Box::new(move_ascii_str_layout()))
} else if resolved_struct == RESOLVED_UTF8_STR {
// both structs structs representing strings have one field - a vector of type u8
(
true,
Some(MoveTypeLayout::Struct(Box::new(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_UTF8_STR),
fields: Box::new(vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)),
)]),
}))),
)
MoveTypeLayout::Struct(Box::new(move_utf8_str_layout()))
} else if resolved_struct == RESOLVED_SUI_ID {
(
true,
Some(MoveTypeLayout::Struct(Box::new(MoveStructLayout {
type_: resolved_to_struct(RESOLVED_SUI_ID),
fields: Box::new(vec![MoveFieldLayout::new(
ident_str!("bytes").into(),
MoveTypeLayout::Address,
)]),
}))),
)
MoveTypeLayout::Struct(Box::new(id::ID::layout()))
} else {
(false, None)
return None;
}
}
SignatureToken::DatatypeInstantiation(struct_inst) => {
let (idx, targs) = &**struct_inst;
let resolved_struct = resolve_struct(view, *idx);
// is option of a primitive
if resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 {
// there is no MoveLayout for this so while we can still report whether a type
// is primitive or not, we can't return the layout
let (is_primitive, inner_layout) = primitive_type(view, type_args, &targs[0]);
let layout =
inner_layout.map(|inner_layout| MoveTypeLayout::Vector(Box::new(inner_layout)));
(is_primitive, layout)
// there is no MoveLayout for this so the type is not a primitive.
MoveTypeLayout::Vector(Box::new(primitive_type(view, type_args, &targs[0])?))
} else {
(false, None)
return None;
}
}

SignatureToken::TypeParameter(idx) => (
type_args
.get(*idx as usize)
.map(is_primitive_type_tag)
.unwrap_or(false),
None,
),

SignatureToken::TypeParameter(idx) => {
layout_of_primitive_typetag(type_args.get(*idx as usize)?)?
}
SignatureToken::Signer
| SignatureToken::Reference(_)
| SignatureToken::MutableReference(_) => (false, None),
}
| SignatureToken::MutableReference(_) => return None,
})
}

fn resolved_to_struct(resolved_type: (&AccountAddress, &IdentStr, &IdentStr)) -> StructTag {
StructTag {
address: *resolved_type.0,
module: resolved_type.1.into(),
name: resolved_type.2.into(),
type_params: vec![],
fn layout_of_primitive_typetag(tag: &TypeTag) -> Option<MoveTypeLayout> {
use MoveTypeLayout as MTL;
if !is_primitive_type_tag(tag) {
return None;
}

Some(match tag {
TypeTag::Bool => MTL::Bool,
TypeTag::U8 => MTL::U8,
TypeTag::U16 => MTL::U16,
TypeTag::U32 => MTL::U32,
TypeTag::U64 => MTL::U64,
TypeTag::U128 => MTL::U128,
TypeTag::U256 => MTL::U256,
TypeTag::Address => MTL::Address,
TypeTag::Signer => return None,
TypeTag::Vector(tag) => MTL::Vector(Box::new(layout_of_primitive_typetag(tag)?)),
TypeTag::Struct(stag) => {
let StructTag {
address,
module,
name,
type_params: type_args,
} = &**stag;
let resolved_struct = (address, module.as_ident_str(), name.as_ident_str());
// is id or..
if resolved_struct == RESOLVED_SUI_ID {
MTL::Struct(Box::new(id::ID::layout()))
} else if resolved_struct == RESOLVED_ASCII_STR {
MTL::Struct(Box::new(move_ascii_str_layout()))
} else if resolved_struct == RESOLVED_UTF8_STR {
MTL::Struct(Box::new(move_utf8_str_layout()))
} else if resolved_struct == RESOLVED_STD_OPTION // is option of a primitive
&& type_args.len() == 1
&& is_primitive_type_tag(&type_args[0])
{
MTL::Vector(Box::new(
layout_of_primitive_typetag(&type_args[0]).unwrap(),
))
} else {
return None;
}
}
})
}

fn resolve_object_arg(idx: usize, arg: &JsonValue) -> Result<ObjectID, anyhow::Error> {
Expand Down Expand Up @@ -725,35 +716,18 @@ fn resolve_call_arg(
arg: &SuiJsonValue,
param: &SignatureToken,
) -> Result<ResolvedCallArg, anyhow::Error> {
let (is_primitive, layout_opt) = primitive_type(view, type_args, param);
if is_primitive {
match layout_opt {
Some(layout) => {
return Ok(ResolvedCallArg::Pure(arg.to_bcs_bytes(&layout).map_err(
|e| {
anyhow!(
"Could not serialize argument of type {:?} at {} into {}. Got error: {:?}",
param,
idx,
layout,
e
)
},
)?));
}
None => {
debug_assert!(
false,
"Should be unreachable. All primitive type function args \
should have a corresponding MoveLayout"
);
bail!(
"Could not serialize argument of type {:?} at {}",
if let Some(layout) = primitive_type(view, type_args, param) {
return Ok(ResolvedCallArg::Pure(arg.to_bcs_bytes(&layout).map_err(
|e| {
anyhow!(
"Could not serialize argument of type {:?} at {} into {}. Got error: {:?}",
param,
idx
);
}
}
idx,
layout,
e
)
},
)?));
}

// in terms of non-primitives we only currently support objects and "flat" (depth == 1) vectors
Expand Down
39 changes: 39 additions & 0 deletions crates/sui-types/src/base_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use move_binary_format::file_format::SignatureToken;
use move_binary_format::CompiledModule;
use move_bytecode_utils::resolve_struct;
use move_core_types::account_address::AccountAddress;
use move_core_types::annotated_value as A;
use move_core_types::ident_str;
use move_core_types::identifier::IdentStr;
use move_core_types::language_storage::ModuleId;
Expand Down Expand Up @@ -440,6 +441,14 @@ pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
if resolved_struct == RESOLVED_SUI_ID {
return true;
}
// is utf8 string
if resolved_struct == RESOLVED_UTF8_STR {
return true;
}
// is ascii string
if resolved_struct == RESOLVED_ASCII_STR {
return true;
}
// is option of a primitive
resolved_struct == RESOLVED_STD_OPTION
&& type_args.len() == 1
Expand Down Expand Up @@ -893,6 +902,36 @@ pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
pub const TX_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("tx_context");
pub const TX_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("TxContext");

pub fn move_ascii_str_layout() -> A::MoveStructLayout {
A::MoveStructLayout {
type_: StructTag {
address: MOVE_STDLIB_ADDRESS,
module: STD_ASCII_MODULE_NAME.to_owned(),
name: STD_ASCII_STRUCT_NAME.to_owned(),
type_params: vec![],
},
fields: Box::new(vec![A::MoveFieldLayout::new(
ident_str!("bytes").into(),
A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
)]),
}
}

pub fn move_utf8_str_layout() -> A::MoveStructLayout {
A::MoveStructLayout {
type_: StructTag {
address: MOVE_STDLIB_ADDRESS,
module: STD_UTF8_MODULE_NAME.to_owned(),
name: STD_UTF8_STRUCT_NAME.to_owned(),
type_params: vec![],
},
fields: Box::new(vec![A::MoveFieldLayout::new(
ident_str!("bytes").into(),
A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
)]),
}
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct TxContext {
/// Signer/sender of the transaction
Expand Down
6 changes: 3 additions & 3 deletions crates/sui/src/client_ptb/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,13 @@ impl<'a> PTBBuilder<'a> {
sp!(loc, arg): Spanned<PTBArg>,
param: &SignatureToken,
) -> PTBResult<Tx::Argument> {
let (is_primitive, layout) = primitive_type(view, ty_args, param);
let layout = primitive_type(view, ty_args, param);

// If it's a primitive value, see if we've already resolved this argument. Otherwise, we
// need to resolve it.
if is_primitive {
if let Some(layout) = layout {
return self
.resolve(loc.wrap(arg), ToPure::new_from_layout(layout.unwrap()))
.resolve(loc.wrap(arg), ToPure::new_from_layout(layout))
.await;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
--move-call 0x2::table::new<address, u64>
--assign table
--move-call 0x2::table::add<address, u64> table @0x2 0
--transfer-objects [table] @0x0
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: crates/sui/tests/ptb_files_tests.rs
expression: "results.join(\"\\n\")"
---
=== PREVIEW ===
╭───────────────────────────────────────────────────────────────╮
PTB Preview
├──────────────────┬────────────────────────────────────────────┤
commandvalues
├──────────────────┼────────────────────────────────────────────┤
move-call0x2::table::new<address, u64> │
assigntable
move-call0x2::table::add<address, u64> table @0x2 0
transfer-objects │ [table] @0x0
╰──────────────────┴────────────────────────────────────────────╯
=== BUILT PTB ===
Input 0: Pure([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2])
Input 1: Pure([0, 0, 0, 0, 0, 0, 0, 0])
Input 2: Pure([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
Command 0: MoveCall(0x0000000000000000000000000000000000000000000000000000000000000002::table::new<address,u64>())
Command 1: MoveCall(0x0000000000000000000000000000000000000000000000000000000000000002::table::add<address,u64>(Result(0),Input(0),Input(1)))
Command 2: TransferObjects([Result(0)],Input(2))

0 comments on commit 69d3653

Please sign in to comment.