diff --git a/Cargo.lock b/Cargo.lock index 4b61dd266544e..51d531373d54e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3282,6 +3282,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "lexical-core" version = "0.7.6" @@ -6985,6 +6991,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bcs", + "leb128", "move-binary-format", "move-core-types", "move-package", @@ -9546,6 +9553,7 @@ dependencies = [ "lazy_static 0.2.11", "lazy_static 1.4.0", "lazycell", + "leb128", "lexical-core", "libc", "libloading", diff --git a/crates/sui-adapter/Cargo.toml b/crates/sui-adapter/Cargo.toml index b717185ca7bac..6c410574da0ae 100644 --- a/crates/sui-adapter/Cargo.toml +++ b/crates/sui-adapter/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.58", features = ["backtrace"] } bcs = "0.1.3" +leb128 = "0.2.5" once_cell = "1.13.1" move-binary-format = { git = "https://github.com/move-language/move", rev = "70b34a66473c34ad30d101290b249f2db3c847a2" } diff --git a/crates/sui-adapter/src/adapter.rs b/crates/sui-adapter/src/adapter.rs index 4d316abe272fa..62654c3d6f4ea 100644 --- a/crates/sui-adapter/src/adapter.rs +++ b/crates/sui-adapter/src/adapter.rs @@ -9,6 +9,7 @@ use std::{ }; use anyhow::Result; +use leb128; use move_binary_format::{ access::ModuleAccess, binary_views::BinaryIndexedView, @@ -78,9 +79,25 @@ pub fn execute< CallArg::Pure(_) => None, CallArg::Object(ObjectArg::ImmOrOwnedObject((id, _, _))) | CallArg::Object(ObjectArg::SharedObject(id)) => { - Some((*id, state_view.read_object(id)?)) + Some(vec![(*id, state_view.read_object(id)?)]) + } + CallArg::ObjVec(vec) => { + if vec.is_empty() { + return None; + } + Some( + vec.iter() + .filter_map(|obj_arg| match obj_arg { + ObjectArg::ImmOrOwnedObject((id, _, _)) + | ObjectArg::SharedObject(id) => { + Some((*id, state_view.read_object(id)?)) + } + }) + .collect(), + ) } }) + .flatten() .collect(); let module = vm.load_module(&module_id, state_view)?; let is_genesis = ctx.digest() == TransactionDigest::genesis(); @@ -983,7 +1000,7 @@ pub fn resolve_and_type_check( .map(|(idx, arg)| { let param_type = ¶meters[idx]; let idx = idx as LocalIndex; - let object_kind = match arg { + let object_arg = match arg { CallArg::Pure(arg) => { if !is_primitive(view, type_args, param_type) { let msg = format!( @@ -1002,135 +1019,66 @@ pub fn resolve_and_type_check( return Ok(arg); } CallArg::Object(ObjectArg::ImmOrOwnedObject(ref_)) => { - InputObjectKind::ImmOrOwnedMoveObject(ref_) + let (o, arg_type, param_type) = serialize_object( + InputObjectKind::ImmOrOwnedMoveObject(ref_), + idx, + param_type, + objects, + &mut object_data, + &mut mutable_ref_objects, + &mut by_value_objects, + &mut object_type_map, + )?; + type_check_struct(view, type_args, idx, arg_type, param_type)?; + o } CallArg::Object(ObjectArg::SharedObject(id)) => { - InputObjectKind::SharedMoveObject(id) - } - }; - - let id = object_kind.object_id(); - let object = match objects.get(&id) { - Some(object) => object.borrow(), - None => { - debug_assert!( - false, - "Object map not populated for arg {} with id {}", - idx, id - ); - return Err(ExecutionErrorKind::InvariantViolation.into()); - } - }; - match object_kind { - InputObjectKind::ImmOrOwnedMoveObject(_) if object.is_shared() => { - let error = format!( - "Argument at index {} populated with shared object id {} \ - but an immutable or owned object was expected", - idx, id - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx, - EntryArgumentErrorKind::ObjectKindMismatch, - ), - error, - )); - } - InputObjectKind::SharedMoveObject(_) if !object.is_shared() => { - let error = format!( - "Argument at index {} populated with an immutable or owned object id {} \ - but an shared object was expected", - idx, id - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx, - EntryArgumentErrorKind::ObjectKindMismatch, - ), - error, - )); - } - _ => (), - } - - let move_object = match &object.data { - Data::Move(m) => m, - Data::Package(_) => { - let error = format!( - "Found module argument, but function expects {:?}", - param_type - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx, - EntryArgumentErrorKind::TypeMismatch, - ), - error, - )); - } - }; - object_data.insert( - id, - (object.owner, object.version(), move_object.child_count()), - ); - let object_arg = move_object.contents().to_vec(); - // check that m.type_ matches the parameter types of the function - let inner_param_type = match ¶m_type { - SignatureToken::Reference(inner_t) => &**inner_t, - SignatureToken::MutableReference(inner_t) => { - if object.is_immutable() { - let error = format!( - "Argument {} is expected to be mutable, immutable object found", - idx - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx, - EntryArgumentErrorKind::InvalidObjectByMuteRef, - ), - error, - )); - } - mutable_ref_objects.insert(idx as LocalIndex, id); - &**inner_t + let (o, arg_type, param_type) = serialize_object( + InputObjectKind::SharedMoveObject(id), + idx, + param_type, + objects, + &mut object_data, + &mut mutable_ref_objects, + &mut by_value_objects, + &mut object_type_map, + )?; + type_check_struct(view, type_args, idx, arg_type, param_type)?; + o } - t @ SignatureToken::Struct(_) - | t @ SignatureToken::StructInstantiation(_, _) - | t @ SignatureToken::TypeParameter(_) => { - match &object.owner { - Owner::AddressOwner(_) | Owner::ObjectOwner(_) => (), - Owner::Shared | Owner::Immutable => { - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx, - EntryArgumentErrorKind::InvalidObjectByValue, - ), - format!( - "Immutable and shared objects cannot be passed by-value, \ - violation found in argument {}", - idx - ), - )); - } + CallArg::ObjVec(vec) => { + if vec.is_empty() { + // bcs representation of the empty vector + return Ok(vec![0]); } - by_value_objects.insert(id); - t - } - t => { - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( + // write length of the vector as uleb128 as it is encoded in BCS and then append + // all (already serialized) object content data + let mut res = vec![]; + leb128::write::unsigned(&mut res, vec.len() as u64).unwrap(); + for arg in vec { + let object_kind = match arg { + ObjectArg::ImmOrOwnedObject(ref_) => { + InputObjectKind::ImmOrOwnedMoveObject(ref_) + } + ObjectArg::SharedObject(id) => InputObjectKind::SharedMoveObject(id), + }; + let (o, arg_type, param_type) = serialize_object( + object_kind, idx, - EntryArgumentErrorKind::TypeMismatch, - ), - format!( - "Found object argument {}, but function expects {:?}", - move_object.type_, t - ), - )); + param_type, + objects, + &mut object_data, + &mut mutable_ref_objects, + &mut by_value_objects, + &mut object_type_map, + )?; + type_check_struct(view, type_args, idx, arg_type, param_type)?; + res.extend(o); + } + res } }; - type_check_struct(view, type_args, idx, &move_object.type_, inner_param_type)?; - object_type_map.insert(id, move_object.type_.module_id()); + Ok(object_arg) }) .collect::, _>>()?; @@ -1152,6 +1100,173 @@ pub fn resolve_and_type_check( }) } +/// Serialize object with ID encoded in object_kind and also verify if various object properties are +/// correct. +fn serialize_object<'a>( + object_kind: InputObjectKind, + idx: LocalIndex, + param_type: &'a SignatureToken, + objects: &'a BTreeMap>, + object_data: &mut BTreeMap)>, + mutable_ref_objects: &mut BTreeMap, + by_value_objects: &mut BTreeSet, + object_type_map: &mut BTreeMap, +) -> Result<(Vec, &'a StructTag, &'a SignatureToken), ExecutionError> { + let object_id = object_kind.object_id(); + let object = match objects.get(&object_id) { + Some(object) => object.borrow(), + None => { + debug_assert!( + false, + "Object map not populated for arg {} with id {}", + idx, object_id + ); + return Err(ExecutionErrorKind::InvariantViolation.into()); + } + }; + + match object_kind { + InputObjectKind::ImmOrOwnedMoveObject(_) if object.is_shared() => { + let error = format!( + "Argument at index {} populated with shared object id {} \ + but an immutable or owned object was expected", + idx, object_id + ); + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error( + idx, + EntryArgumentErrorKind::ObjectKindMismatch, + ), + error, + )); + } + InputObjectKind::SharedMoveObject(_) if !object.is_shared() => { + let error = format!( + "Argument at index {} populated with an immutable or owned object id {} \ + but an shared object was expected", + idx, object_id + ); + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error( + idx, + EntryArgumentErrorKind::ObjectKindMismatch, + ), + error, + )); + } + _ => (), + } + + let move_object = match &object.data { + Data::Move(m) => m, + Data::Package(_) => { + let for_vector = matches!(param_type, SignatureToken::Vector { .. }); + let error = format!( + "Found module {} argument, but function expects {:?}", + if for_vector { "element in vector" } else { "" }, + param_type + ); + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error(idx, EntryArgumentErrorKind::TypeMismatch), + error, + )); + } + }; + + // check that m.type_ matches the parameter types of the function + let inner_param_type = inner_param_type( + object, + object_id, + idx, + param_type, + &move_object.type_, + mutable_ref_objects, + by_value_objects, + )?; + + object_type_map.insert(object_id, move_object.type_.module_id()); + object_data.insert( + object_id, + (object.owner, object.version(), move_object.child_count()), + ); + Ok(( + move_object.contents().to_vec(), + &move_object.type_, + inner_param_type, + )) +} + +/// Get "inner" type of an object passed as argument (e.g., an inner type of a reference or of a +/// vector) and also verify if various object properties are correct. +fn inner_param_type<'a>( + object: &Object, + object_id: ObjectID, + idx: LocalIndex, + param_type: &'a SignatureToken, + arg_type: &StructTag, + mutable_ref_objects: &mut BTreeMap, + by_value_objects: &mut BTreeSet, +) -> Result<&'a SignatureToken, ExecutionError> { + match ¶m_type { + SignatureToken::Reference(inner_t) => Ok(&**inner_t), + SignatureToken::MutableReference(inner_t) => { + if object.is_immutable() { + let error = format!( + "Argument {} is expected to be mutable, immutable object found", + idx + ); + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error( + idx, + EntryArgumentErrorKind::InvalidObjectByMuteRef, + ), + error, + )); + } + mutable_ref_objects.insert(idx as LocalIndex, object_id); + Ok(&**inner_t) + } + SignatureToken::Vector(inner_t) => inner_param_type( + object, + object_id, + idx, + inner_t, + arg_type, + mutable_ref_objects, + by_value_objects, + ), + t @ SignatureToken::Struct(_) + | t @ SignatureToken::StructInstantiation(_, _) + | t @ SignatureToken::TypeParameter(_) => { + match &object.owner { + Owner::AddressOwner(_) | Owner::ObjectOwner(_) => (), + Owner::Shared | Owner::Immutable => { + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error( + idx, + EntryArgumentErrorKind::InvalidObjectByValue, + ), + format!( + "Immutable and shared objects cannot be passed by-value, \ + violation found in argument {}", + idx + ), + )); + } + } + by_value_objects.insert(object_id); + Ok(t) + } + t => Err(ExecutionError::new_with_source( + ExecutionErrorKind::entry_argument_error(idx, EntryArgumentErrorKind::TypeMismatch), + format!( + "Found object argument {}, but function expects {:?}", + arg_type, t + ), + )), + } +} + /// Check rules for shared object + by-value child rules and rules for by-value shared object rules: /// - For each pair of a shared object and a descendant of it (through object ownership), if the /// descendant is used by-value, at least one of the types of the shared object and the descendant diff --git a/crates/sui-core/src/unit_tests/authority_tests.rs b/crates/sui-core/src/unit_tests/authority_tests.rs index c6a04206fa6e7..d42ee54369268 100644 --- a/crates/sui-core/src/unit_tests/authority_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_tests.rs @@ -42,25 +42,37 @@ pub enum TestCallArg { Object(ObjectID), U64(u64), Address(SuiAddress), + PrimVec(Vec), + ObjVec(Vec), } impl TestCallArg { pub async fn to_call_arg(self, state: &AuthorityState) -> CallArg { match self { Self::Object(object_id) => { - let object = state.get_object(&object_id).await.unwrap().unwrap(); - if object.is_shared() { - CallArg::Object(ObjectArg::SharedObject(object_id)) - } else { - CallArg::Object(ObjectArg::ImmOrOwnedObject( - object.compute_object_reference(), - )) - } + CallArg::Object(Self::call_arg_from_id(object_id, state).await) } Self::U64(value) => CallArg::Pure(bcs::to_bytes(&value).unwrap()), Self::Address(addr) => { CallArg::Pure(bcs::to_bytes(&AccountAddress::from(addr)).unwrap()) } + Self::PrimVec(value) => CallArg::Pure(bcs::to_bytes(&value).unwrap()), + Self::ObjVec(vec) => { + let mut refs = vec![]; + for object_id in vec { + refs.push(Self::call_arg_from_id(object_id, state).await) + } + CallArg::ObjVec(refs) + } + } + } + + async fn call_arg_from_id(object_id: ObjectID, state: &AuthorityState) -> ObjectArg { + let object = state.get_object(&object_id).await.unwrap().unwrap(); + if object.is_shared() { + ObjectArg::SharedObject(object_id) + } else { + ObjectArg::ImmOrOwnedObject(object.compute_object_reference()) } } } @@ -507,6 +519,19 @@ async fn test_objected_owned_gas() { pub async fn send_and_confirm_transaction( authority: &AuthorityState, transaction: Transaction, +) -> Result { + send_and_confirm_transaction_with_shared( + authority, + transaction, + false, /* no shared objects */ + ) + .await +} + +pub async fn send_and_confirm_transaction_with_shared( + authority: &AuthorityState, + transaction: Transaction, + with_shared: bool, // transaction includes shared objects ) -> Result { // Make the initial request let response = authority.handle_transaction(transaction.clone()).await?; @@ -519,6 +544,11 @@ pub async fn send_and_confirm_transaction( .append(vote.auth_sign_info.authority, vote.auth_sign_info.signature) .unwrap() .unwrap(); + + if with_shared { + send_consensus(authority, &certificate).await; + } + // Submit the confirmation. *Now* execution actually happens, and it should fail when we try to look up our dummy module. // we unfortunately don't get a very descriptive error message, but we can at least see that something went wrong inside the VM authority.handle_certificate(certificate).await @@ -2032,6 +2062,22 @@ fn init_certified_transaction( .unwrap() } +#[cfg(test)] +async fn send_consensus(authority: &AuthorityState, cert: &CertifiedTransaction) { + authority + .handle_consensus_transaction( + // TODO [2533]: use this once integrating Narwhal reconfiguration + &narwhal_consensus::ConsensusOutput { + certificate: narwhal_types::Certificate::default(), + consensus_index: narwhal_types::SequenceNumber::default(), + }, + /* last_consensus_index */ ExecutionIndices::default(), + ConsensusTransaction::new_certificate_message(&authority.name, cert.clone()), + ) + .await + .unwrap(); +} + pub async fn call_move( authority: &AuthorityState, gas_object_id: &ObjectID, @@ -2042,6 +2088,33 @@ pub async fn call_move( function: &'_ str, type_args: Vec, test_args: Vec, +) -> SuiResult { + call_move_with_shared( + authority, + gas_object_id, + sender, + sender_key, + package, + module, + function, + type_args, + test_args, + false, // no shared objects + ) + .await +} + +pub async fn call_move_with_shared( + authority: &AuthorityState, + gas_object_id: &ObjectID, + sender: &SuiAddress, + sender_key: &AccountKeyPair, + package: &ObjectRef, + module: &'_ str, + function: &'_ str, + type_args: Vec, + test_args: Vec, + with_shared: bool, // Move call includes shared objects ) -> SuiResult { let gas_object = authority.get_object(gas_object_id).await.unwrap(); let gas_object_ref = gas_object.unwrap().compute_object_reference(); @@ -2063,7 +2136,8 @@ pub async fn call_move( let signature = Signature::new(&data, sender_key); let transaction = Transaction::new(data, signature); - let response = send_and_confirm_transaction(authority, transaction).await?; + let response = + send_and_confirm_transaction_with_shared(authority, transaction, with_shared).await?; Ok(response.signed_effects.unwrap().effects) } @@ -2176,18 +2250,7 @@ async fn shared_object() { assert!(matches!(result, Err(SuiError::ObjectErrors { .. }))); // Sequence the certificate to assign a sequence number to the shared object. - authority - .handle_consensus_transaction( - // TODO [2533]: use this once integrating Narwhal reconfiguration - &narwhal_consensus::ConsensusOutput { - certificate: narwhal_types::Certificate::default(), - consensus_index: narwhal_types::SequenceNumber::default(), - }, - /* last_consensus_index */ ExecutionIndices::default(), - ConsensusTransaction::new_certificate_message(&authority.name, certificate.clone()), - ) - .await - .unwrap(); + send_consensus(&authority, &certificate).await; let shared_object_version = authority .db() @@ -2257,21 +2320,6 @@ async fn test_consensus_message_processed() { ) .await; - async fn send_consensus(authority: &AuthorityState, cert: &CertifiedTransaction) { - authority - .handle_consensus_transaction( - // TODO [2533]: use this once integrating Narwhal reconfiguration - &narwhal_consensus::ConsensusOutput { - certificate: narwhal_types::Certificate::default(), - consensus_index: narwhal_types::SequenceNumber::default(), - }, - /* last_consensus_index */ ExecutionIndices::default(), - ConsensusTransaction::new_certificate_message(&authority.name, cert.clone()), - ) - .await - .unwrap(); - } - async fn handle_cert( authority: &AuthorityState, cert: &CertifiedTransaction, diff --git a/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.toml b/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.toml new file mode 100644 index 0000000000000..85a44fbbb8d4e --- /dev/null +++ b/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "entry_point_vector" +version = "0.0.1" + +[dependencies] +Sui = { local = "../../../../../sui-framework" } + +[addresses] +entry_point_vector = "0x0" diff --git a/crates/sui-core/src/unit_tests/data/entry_point_vector/sources/objects_vector.move b/crates/sui-core/src/unit_tests/data/entry_point_vector/sources/objects_vector.move new file mode 100644 index 0000000000000..0559b166f694c --- /dev/null +++ b/crates/sui-core/src/unit_tests/data/entry_point_vector/sources/objects_vector.move @@ -0,0 +1,117 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module entry_point_vector::entry_point_vector { + use sui::object::{Self, UID}; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + use std::vector; + + struct Obj has key { + id: UID, + value: u64 + } + + struct AnotherObj has key { + id: UID, + value: u64 + } + + public entry fun mint(v: u64, ctx: &mut TxContext) { + transfer::transfer( + Obj { + id: object::new(ctx), + value: v, + }, + tx_context::sender(ctx), + ) + } + + public entry fun mint_another(v: u64, ctx: &mut TxContext) { + transfer::transfer( + AnotherObj { + id: object::new(ctx), + value: v, + }, + tx_context::sender(ctx), + ) + } + + public entry fun mint_child(v: u64, parent: &mut Obj, ctx: &mut TxContext) { + transfer::transfer_to_object( + Obj { + id: object::new(ctx), + value: v, + }, + parent, + ) + } + + public entry fun mint_shared(v: u64, ctx: &mut TxContext) { + transfer::share_object( + Obj { + id: object::new(ctx), + value: v, + } + ) + } + + + public entry fun prim_vec_len(v: vector, _: &mut TxContext) { + assert!(vector::length(&v) == 2, 0); + } + + public entry fun obj_vec_empty(v: vector, _: &mut TxContext) { + vector::destroy_empty(v); + } + + public entry fun obj_vec_destroy(v: vector, _: &mut TxContext) { + assert!(vector::length(&v) == 1, 0); + let Obj {id, value} = vector::pop_back(&mut v); + assert!(value == 42, 0); + object::delete(id); + vector::destroy_empty(v); + } + + public entry fun two_obj_vec_destroy(v: vector, _: &mut TxContext) { + assert!(vector::length(&v) == 2, 0); + let Obj {id, value} = vector::pop_back(&mut v); + assert!(value == 42, 0); + object::delete(id); + let Obj {id, value} = vector::pop_back(&mut v); + assert!(value == 7, 0); + object::delete(id); + vector::destroy_empty(v); + } + + public entry fun same_objects(o: Obj, v: vector, _: &mut TxContext) { + let Obj {id, value} = o; + assert!(value == 42, 0); + object::delete(id); + let Obj {id, value} = vector::pop_back(&mut v); + assert!(value == 42, 0); + object::delete(id); + vector::destroy_empty(v); + } + + public entry fun same_objects_ref(o: &Obj, v: vector, _: &mut TxContext) { + assert!(o.value == 42, 0); + let Obj {id, value: _} = vector::pop_back(&mut v); + object::delete(id); + vector::destroy_empty(v); + } + + public entry fun child_access(child: Obj, v: vector, _: &mut TxContext) { + let Obj {id, value} = child; + assert!(value == 42, 0); + object::delete(id); + let Obj {id, value} = vector::pop_back(&mut v); + assert!(value == 42, 0); + object::delete(id); + vector::destroy_empty(v); + } + + public entry fun type_param_vec_empty(v: vector, _: &mut TxContext) { + vector::destroy_empty(v); + } +} diff --git a/crates/sui-core/src/unit_tests/move_integration_tests.rs b/crates/sui-core/src/unit_tests/move_integration_tests.rs index 14f4d4a616b72..167d5ea5041a5 100644 --- a/crates/sui-core/src/unit_tests/move_integration_tests.rs +++ b/crates/sui-core/src/unit_tests/move_integration_tests.rs @@ -4,9 +4,11 @@ use super::*; use crate::authority::authority_tests::{ - call_move, init_state_with_ids, send_and_confirm_transaction, TestCallArg, + call_move, call_move_with_shared, init_state_with_ids, send_and_confirm_transaction, + TestCallArg, }; +use move_core_types::language_storage::TypeTag; use move_package::BuildConfig; use sui_types::{ crypto::{get_key_pair, AccountKeyPair, Signature}, @@ -15,8 +17,8 @@ use sui_types::{ object::OBJECT_START_VERSION, }; -use std::env; use std::path::PathBuf; +use std::{env, str::FromStr}; const MAX_GAS: u64 = 10000; @@ -445,6 +447,472 @@ async fn test_object_owning_another_object() { assert_eq!(effects.deleted.len(), 2); } +#[tokio::test] +async fn test_entry_point_vector_empty() { + let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); + let gas = ObjectID::random(); + let authority = init_state_with_ids(vec![(sender, gas)]).await; + + let package = build_and_publish_test_package( + &authority, + &sender, + &sender_key, + &gas, + "entry_point_vector", + ) + .await; + + // call a function with an empty vector + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "obj_vec_empty", + vec![], + vec![TestCallArg::ObjVec(vec![])], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + + // call a function with an empty vector whose type is generic + let type_tag = + TypeTag::from_str(format!("{}::entry_point_vector::Obj", package.0).as_str()).unwrap(); + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "type_param_vec_empty", + vec![type_tag], + vec![TestCallArg::ObjVec(vec![])], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); +} + +#[tokio::test] +async fn test_entry_point_vector_primitive() { + let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); + let gas = ObjectID::random(); + let authority = init_state_with_ids(vec![(sender, gas)]).await; + + let package = build_and_publish_test_package( + &authority, + &sender, + &sender_key, + &gas, + "entry_point_vector", + ) + .await; + + // just a test call with vector of 2 primitive values and check its length in the entry function + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "prim_vec_len", + vec![], + vec![TestCallArg::PrimVec(vec![7, 42])], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); +} + +#[tokio::test] +async fn test_entry_point_vector() { + let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); + let gas = ObjectID::random(); + let authority = init_state_with_ids(vec![(sender, gas)]).await; + + let package = build_and_publish_test_package( + &authority, + &sender, + &sender_key, + &gas, + "entry_point_vector", + ) + .await; + + // mint an owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing one owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "obj_vec_destroy", + vec![], + vec![TestCallArg::ObjVec(vec![obj_id])], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + + // mint an owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing the same owned object as another one passed as + // argument + let result = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "same_objects", + vec![], + vec![ + TestCallArg::Object(obj_id), + TestCallArg::ObjVec(vec![obj_id]), + ], + ) + .await; + // should fail as we have the same object passed in vector and as a separate by-value argument + assert!( + matches!( + result.clone().err().unwrap(), + SuiError::DuplicateObjectRefInput { .. } + ), + "{:?}", + result + ); + + // mint an owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing the same owned object as another one passed as + // a reference argument + let result = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "same_objects_ref", + vec![], + vec![ + TestCallArg::Object(obj_id), + TestCallArg::ObjVec(vec![obj_id]), + ], + ) + .await; + assert!( + matches!( + result.clone().err().unwrap(), + SuiError::DuplicateObjectRefInput { .. } + ), + "{:?}", + result + ); + + // mint a parent object and a child object and make sure that parent stored in the vector + // authenticates the child passed by-value + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (parent_id, _, _) = effects.created[0].0; + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint_child", + vec![], + vec![TestCallArg::U64(42), TestCallArg::Object(parent_id)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (child_id, _, _) = effects.created[0].0; + // call a function with a vector containing the same owned object as another one passed as + // a reference argument + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "child_access", + vec![], + vec![ + TestCallArg::Object(child_id), + TestCallArg::ObjVec(vec![parent_id]), + ], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); +} + +#[tokio::test] +async fn test_entry_point_vector_error() { + let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); + let gas = ObjectID::random(); + let authority = init_state_with_ids(vec![(sender, gas)]).await; + + let package = build_and_publish_test_package( + &authority, + &sender, + &sender_key, + &gas, + "entry_point_vector", + ) + .await; + + // mint an owned object of a wrong type + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint_another", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing one owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "obj_vec_destroy", + vec![], + vec![TestCallArg::ObjVec(vec![obj_id])], + ) + .await + .unwrap(); + // should fail as we passed object of the wrong type + assert!( + matches!(effects.status, ExecutionStatus::Failure { .. }), + "{:?}", + effects.status + ); + + // mint two objects - one of a wrong type and one of the correct type + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint_another", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (wrong_obj_id, _, _) = effects.created[0].0; + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (correct_obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing one owned object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "two_obj_vec_destroy", + vec![], + vec![TestCallArg::ObjVec(vec![wrong_obj_id, correct_obj_id])], + ) + .await + .unwrap(); + // should fail as we passed object of the wrong type as the first element of the vector + assert!( + matches!(effects.status, ExecutionStatus::Failure { .. }), + "{:?}", + effects.status + ); + + // mint a shared object + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "mint_shared", + vec![], + vec![TestCallArg::U64(42)], + ) + .await + .unwrap(); + assert!( + matches!(effects.status, ExecutionStatus::Success { .. }), + "{:?}", + effects.status + ); + let (shared_obj_id, _, _) = effects.created[0].0; + // call a function with a vector containing one shared object + let effects = call_move_with_shared( + &authority, + &gas, + &sender, + &sender_key, + &package, + "entry_point_vector", + "obj_vec_destroy", + vec![], + vec![TestCallArg::ObjVec(vec![shared_obj_id])], + true, // shared object in arguments + ) + .await + .unwrap(); + // should fail as we do not support shared objects in vectors + assert!( + matches!(effects.status, ExecutionStatus::Failure { .. }), + "{:?}", + effects.status + ); +} + pub async fn build_and_try_publish_test_package( authority: &AuthorityState, sender: &SuiAddress, diff --git a/crates/sui-core/tests/staged/sui.yaml b/crates/sui-core/tests/staged/sui.yaml index ab4273c93bdc4..d17bacdf9dbc4 100644 --- a/crates/sui-core/tests/staged/sui.yaml +++ b/crates/sui-core/tests/staged/sui.yaml @@ -45,6 +45,11 @@ CallArg: Object: NEWTYPE: TYPENAME: ObjectArg + 2: + ObjVec: + NEWTYPE: + SEQ: + TYPENAME: ObjectArg ChangeEpoch: STRUCT: - epoch: U64 diff --git a/crates/sui-json-rpc-types/src/lib.rs b/crates/sui-json-rpc-types/src/lib.rs index 7b5775eb6aca5..f90cdaf252251 100644 --- a/crates/sui-json-rpc-types/src/lib.rs +++ b/crates/sui-json-rpc-types/src/lib.rs @@ -1404,12 +1404,20 @@ impl TryFrom for SuiTransactionKind { .into_iter() .map(|arg| match arg { CallArg::Pure(p) => SuiJsonValue::from_bcs_bytes(&p), - CallArg::Object(ObjectArg::ImmOrOwnedObject((id, _, _))) => { - SuiJsonValue::new(Value::String(id.to_hex_literal())) - } - CallArg::Object(ObjectArg::SharedObject(id)) => { + CallArg::Object(ObjectArg::ImmOrOwnedObject((id, _, _))) + | CallArg::Object(ObjectArg::SharedObject(id)) => { SuiJsonValue::new(Value::String(id.to_hex_literal())) } + CallArg::ObjVec(vec) => SuiJsonValue::new(Value::Array( + vec.iter() + .map(|obj_arg| match obj_arg { + ObjectArg::ImmOrOwnedObject((id, _, _)) + | ObjectArg::SharedObject(id) => { + Value::String(id.to_hex_literal()) + } + }) + .collect(), + )), }) .collect::, _>>()?, }), diff --git a/crates/sui-types/src/messages.rs b/crates/sui-types/src/messages.rs index cd199de9b7d0e..47093ad1a0930 100644 --- a/crates/sui-types/src/messages.rs +++ b/crates/sui-types/src/messages.rs @@ -51,7 +51,8 @@ pub enum CallArg { Pure(Vec), // an object Object(ObjectArg), - // TODO support more than one object (object vector of some sort) + // a vector of objects + ObjVec(Vec), } #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] @@ -134,12 +135,26 @@ impl SingleTransactionKind { pub fn shared_input_objects(&self) -> impl Iterator { match &self { - Self::Call(MoveCall { arguments, .. }) => { - Either::Left(arguments.iter().filter_map(|arg| match arg { - CallArg::Pure(_) | CallArg::Object(ObjectArg::ImmOrOwnedObject(_)) => None, - CallArg::Object(ObjectArg::SharedObject(id)) => Some(id), - })) - } + Self::Call(MoveCall { arguments, .. }) => Either::Left( + arguments + .iter() + .filter_map(|arg| match arg { + CallArg::Pure(_) | CallArg::Object(ObjectArg::ImmOrOwnedObject(_)) => None, + CallArg::Object(ObjectArg::SharedObject(id)) => Some(vec![id]), + CallArg::ObjVec(vec) => Some( + vec.iter() + .filter_map(|obj_arg| { + if let ObjectArg::SharedObject(id) = obj_arg { + Some(id) + } else { + None + } + }) + .collect(), + ), + }) + .flatten(), + ), _ => Either::Right(std::iter::empty()), } } @@ -167,12 +182,25 @@ impl SingleTransactionKind { .filter_map(|arg| match arg { CallArg::Pure(_) => None, CallArg::Object(ObjectArg::ImmOrOwnedObject(object_ref)) => { - Some(InputObjectKind::ImmOrOwnedMoveObject(*object_ref)) + Some(vec![InputObjectKind::ImmOrOwnedMoveObject(*object_ref)]) } CallArg::Object(ObjectArg::SharedObject(id)) => { - Some(InputObjectKind::SharedMoveObject(*id)) + Some(vec![InputObjectKind::SharedMoveObject(*id)]) } + CallArg::ObjVec(vec) => Some( + vec.iter() + .map(|obj_arg| match obj_arg { + ObjectArg::ImmOrOwnedObject(object_ref) => { + InputObjectKind::ImmOrOwnedMoveObject(*object_ref) + } + ObjectArg::SharedObject(id) => { + InputObjectKind::SharedMoveObject(*id) + } + }) + .collect(), + ), }) + .flatten() .chain([InputObjectKind::MovePackage(package.0)]) .collect(), Self::Publish(MoveModulePublish { modules }) => { diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.exp b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.exp new file mode 100644 index 0000000000000..d0aeb8a72fe86 --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.exp @@ -0,0 +1,5 @@ +processed 1 task + +task 0 'publish'. lines 6-15: +Error: Transaction Effects Status: Sui Move Bytecode Verification Error. Please run the Sui Move Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: SuiMoveVerificationError, source: Some("Invalid entry point parameter type. Expected primitive or object type. Got: &mut vector") } } diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.mvir b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.mvir new file mode 100644 index 0000000000000..856a9c07ebf63 --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_mut_ref_vector.mvir @@ -0,0 +1,15 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// invalid, a mutable reference to vector of objects + +//# publish +module 0x0.m { + import 0x2.tx_context; + + public entry no(s: &mut vector, ctx: &mut tx_context.TxContext) { + label l0: + abort 0; + } + +} diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.exp b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.exp new file mode 100644 index 0000000000000..6fa7194d6df0a --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.exp @@ -0,0 +1,5 @@ +processed 1 task + +task 0 'publish'. lines 6-15: +Error: Transaction Effects Status: Sui Move Bytecode Verification Error. Please run the Sui Move Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: SuiMoveVerificationError, source: Some("Invalid entry point parameter type. Expected primitive or object type. Got: &vector") } } diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.mvir b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.mvir new file mode 100644 index 0000000000000..b4a31cbf752cf --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/generic_obj_ref_vector.mvir @@ -0,0 +1,15 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// invalid, a mutable reference to vector of objects + +//# publish +module 0x0.m { + import 0x2.tx_context; + + public entry no(s: &vector, ctx: &mut tx_context.TxContext) { + label l0: + abort 0; + } + +} diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/nested_key_generic_vector_param.exp b/crates/sui-verifier-transactional-tests/tests/entry_points/nested_key_generic_vector_param.exp index 936eee23f807c..8bc1eb2c44c0e 100644 --- a/crates/sui-verifier-transactional-tests/tests/entry_points/nested_key_generic_vector_param.exp +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/nested_key_generic_vector_param.exp @@ -1,5 +1,5 @@ processed 1 task task 0 'publish'. lines 4-13: -created: object(103) -written: object(102) +Error: Transaction Effects Status: Sui Move Bytecode Verification Error. Please run the Sui Move Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: SuiMoveVerificationError, source: Some("Invalid entry point parameter type. Expected primitive or object type. Got: vector>") } } diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.exp b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.exp new file mode 100644 index 0000000000000..d1b30ac4bf9a3 --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.exp @@ -0,0 +1,5 @@ +processed 1 task + +task 0 'publish'. lines 6-18: +Error: Transaction Effects Status: Sui Move Bytecode Verification Error. Please run the Sui Move Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: SuiMoveVerificationError, source: Some("Invalid entry point parameter type. Expected primitive or object type. Got: &mut vector<_::m::S>") } } diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.mvir b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.mvir new file mode 100644 index 0000000000000..0dd49847a3cad --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_mut_ref_vector.mvir @@ -0,0 +1,18 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// invalid, a mutable reference to vector of objects + +//# publish +module 0x0.m { + import 0x2.object; + import 0x2.tx_context; + + struct S has key { id: object.UID } + + public entry no(s: &mut vector, ctx: &mut tx_context.TxContext) { + label l0: + abort 0; + } + +} diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.exp b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.exp new file mode 100644 index 0000000000000..8d8a3cb70747f --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.exp @@ -0,0 +1,5 @@ +processed 1 task + +task 0 'publish'. lines 6-18: +Error: Transaction Effects Status: Sui Move Bytecode Verification Error. Please run the Sui Move Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: SuiMoveVerificationError, source: Some("Invalid entry point parameter type. Expected primitive or object type. Got: &vector<_::m::S>") } } diff --git a/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.mvir b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.mvir new file mode 100644 index 0000000000000..1f9b1bfb4acdd --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/entry_points/obj_ref_vector.mvir @@ -0,0 +1,18 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// invalid, a reference to vector of objects + +//# publish +module 0x0.m { + import 0x2.object; + import 0x2.tx_context; + + struct S has key { id: object.UID } + + public entry no(s: &vector, ctx: &mut tx_context.TxContext) { + label l0: + abort 0; + } + +} diff --git a/crates/sui-verifier/src/entry_points_verifier.rs b/crates/sui-verifier/src/entry_points_verifier.rs index 7c37c0e4e3a49..c5e360e157f68 100644 --- a/crates/sui-verifier/src/entry_points_verifier.rs +++ b/crates/sui-verifier/src/entry_points_verifier.rs @@ -182,11 +182,10 @@ fn verify_param_type( function_type_args: &[AbilitySet], param: &SignatureToken, ) -> Result<(), String> { - if is_primitive(view, function_type_args, param) { - return Ok(()); - } - - if is_object(view, function_type_args, param)? { + if is_primitive(view, function_type_args, param) + || is_object(view, function_type_args, param)? + || is_object_vector(view, function_type_args, param)? + { Ok(()) } else { Err(format!( @@ -259,13 +258,25 @@ pub fn is_object( ) -> Result { use SignatureToken as S; match t { - S::Reference(inner) | S::MutableReference(inner) | S::Vector(inner) => { + S::Reference(inner) | S::MutableReference(inner) => { is_object(view, function_type_args, inner) } _ => is_object_struct(view, function_type_args, t), } } +pub fn is_object_vector( + view: &BinaryIndexedView, + function_type_args: &[AbilitySet], + t: &SignatureToken, +) -> Result { + use SignatureToken as S; + match t { + S::Vector(inner) => is_object_struct(view, function_type_args, inner), + _ => is_object_struct(view, function_type_args, t), + } +} + fn is_object_struct( view: &BinaryIndexedView, function_type_args: &[AbilitySet], diff --git a/crates/workspace-hack/Cargo.toml b/crates/workspace-hack/Cargo.toml index 70dbeb8b93f72..a324b0b0360a4 100644 --- a/crates/workspace-hack/Cargo.toml +++ b/crates/workspace-hack/Cargo.toml @@ -255,6 +255,7 @@ keccak = { version = "0.1", default-features = false } kstring = { version = "1", features = ["max_inline", "serde"] } lazy_static-6f8ce4dd05d13bba = { package = "lazy_static", version = "0.2", default-features = false } lazy_static-dff4ba8e3ae991db = { package = "lazy_static", version = "1", default-features = false } +leb128 = { version = "0.2", default-features = false } lexical-core = { version = "0.7", features = ["arrayvec", "correct", "ryu", "static_assertions", "std", "table"] } libc = { version = "0.2", features = ["std"] } libm = { version = "0.2" } @@ -872,6 +873,7 @@ kstring = { version = "1", features = ["max_inline", "serde"] } lazy_static-6f8ce4dd05d13bba = { package = "lazy_static", version = "0.2", default-features = false } lazy_static-dff4ba8e3ae991db = { package = "lazy_static", version = "1", default-features = false } lazycell = { version = "1", default-features = false } +leb128 = { version = "0.2", default-features = false } lexical-core = { version = "0.7", features = ["arrayvec", "correct", "ryu", "static_assertions", "std", "table"] } libc = { version = "0.2", features = ["std"] } libloading = { version = "0.7", default-features = false }