diff --git a/crates/sui-adapter/src/programmable_transactions/context.rs b/crates/sui-adapter/src/programmable_transactions/context.rs index 1484d9c718af8..421436472d169 100644 --- a/crates/sui-adapter/src/programmable_transactions/context.rs +++ b/crates/sui-adapter/src/programmable_transactions/context.rs @@ -7,10 +7,7 @@ use std::{ marker::PhantomData, }; -use move_binary_format::{ - errors::{Location, VMError}, - file_format::LocalIndex, -}; +use move_binary_format::errors::{Location, VMError}; use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; use move_vm_runtime::{move_vm::MoveVM, session::Session}; use sui_framework::natives::object_runtime::{max_event_error, ObjectRuntime, RuntimeResults}; @@ -21,7 +18,7 @@ use sui_types::{ coin::Coin, error::{ExecutionError, ExecutionErrorKind}, gas::SuiGasStatus, - messages::{Argument, CallArg, EntryArgumentErrorKind, ObjectArg}, + messages::{Argument, CallArg, CommandArgumentError, ObjectArg}, object::{MoveObject, Object, Owner}, storage::{ObjectChange, SingleTxContext, Storage, WriteKind}, }; @@ -186,26 +183,37 @@ where Ok(()) } - /// Take the argument value, setting its value to None, making it unavailable + /// Get the argument value. Cloning the value if it is copyable, and setting its value to None + /// if it is not (making it unavailable). /// Errors if out of bounds, if the argument is borrowed, if it is unavailable (already taken), /// or if it is an object that cannot be taken by value (shared or immutable) - pub fn take_arg( + pub fn by_value_arg( &mut self, command_kind: CommandKind<'_>, arg_idx: usize, arg: Argument, ) -> Result { + self.by_value_arg_(command_kind, arg) + .map_err(|e| command_argument_error(e, arg_idx)) + } + fn by_value_arg_( + &mut self, + command_kind: CommandKind<'_>, + arg: Argument, + ) -> Result { if matches!(arg, Argument::GasCoin) && !matches!(command_kind, CommandKind::TransferObjects) { - panic!("cannot take gas") + return Err(CommandArgumentError::InvalidGasCoinUsage); } if self.arg_is_borrowed(&arg) { - panic!("taken borrowed value") - } - let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::Take)?; - if val_opt.is_none() { - panic!("taken value") + return Err(CommandArgumentError::InvalidUsageOfBorrowedValue); } + let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::ByValue)?; + let is_copyable = if let Some(val) = val_opt { + val.is_copyable() + } else { + return Err(CommandArgumentError::InvalidUsageOfTakenValue); + }; if matches!( input_metadata_opt, Some(InputObjectMetadata { @@ -213,20 +221,14 @@ where .. }) ) { - let error = format!( - "Immutable and shared objects cannot be passed by-value, \ - violation found in argument {}", - arg_idx - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - arg_idx as LocalIndex, - EntryArgumentErrorKind::InvalidObjectByValue, - ), - error, - )); + return Err(CommandArgumentError::InvalidObjectByValue); } - V::try_from_value(val_opt.take().unwrap()) + let val = if is_copyable { + val_opt.as_ref().unwrap().clone() + } else { + val_opt.take().unwrap() + }; + V::try_from_value(val) } /// Mimic a mutable borrow by taking the argument value, setting its value to None, @@ -239,13 +241,20 @@ where arg_idx: usize, arg: Argument, ) -> Result { + self.borrow_arg_mut_(arg) + .map_err(|e| command_argument_error(e, arg_idx)) + } + fn borrow_arg_mut_( + &mut self, + arg: Argument, + ) -> Result { if self.arg_is_borrowed(&arg) { - panic!("mutable args can only be used once in a given command") + return Err(CommandArgumentError::InvalidUsageOfBorrowedValue); } self.borrowed.insert(arg, /* is_mut */ true); let (input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowMut)?; if val_opt.is_none() { - panic!("taken value") + return Err(CommandArgumentError::InvalidUsageOfTakenValue); } if matches!( input_metadata_opt, @@ -254,55 +263,30 @@ where .. }) ) { - let error = format!( - "Argument {} is expected to be mutable, immutable object found", - arg_idx - ); - return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - arg_idx as LocalIndex, - EntryArgumentErrorKind::InvalidObjectByMuteRef, - ), - error, - )); + return Err(CommandArgumentError::InvalidObjectByMutRef); } V::try_from_value(val_opt.take().unwrap()) } - /// Clone the argument value without setting its value to None - /// Errors if out of bounds, if the argument is mutably borrowed, - /// or if it is unavailable (already taken) - pub fn clone_arg( - &mut self, - _arg_idx: usize, - arg: Argument, - ) -> Result { - if self.arg_is_mut_borrowed(&arg) { - panic!("mutable args can only be used once in a given command") - } - let (_input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::Clone)?; - if val_opt.is_none() { - panic!("taken value") - } - let val = val_opt.as_ref().unwrap().clone(); - V::try_from_value(val) - } - /// Mimics an immutable borrow by cloning the argument value without setting its value to None /// Errors if out of bounds, if the argument is mutably borrowed, /// or if it is unavailable (already taken) pub fn borrow_arg( &mut self, - _arg_idx: usize, + arg_idx: usize, arg: Argument, ) -> Result { + self.borrow_arg_(arg) + .map_err(|e| command_argument_error(e, arg_idx)) + } + fn borrow_arg_(&mut self, arg: Argument) -> Result { if self.arg_is_mut_borrowed(&arg) { - panic!("mutable args can only be used once in a given command") + return Err(CommandArgumentError::InvalidUsageOfBorrowedValue); } self.borrowed.insert(arg, /* is_mut */ false); let (_input_metadata_opt, val_opt) = self.borrow_mut(arg, UsageKind::BorrowImm)?; if val_opt.is_none() { - panic!("taken value") + return Err(CommandArgumentError::InvalidUsageOfTakenValue); } V::try_from_value(val_opt.as_ref().unwrap().clone()) } @@ -315,7 +299,9 @@ where The take+restore is an implementation detail of mutable references" ); // restore is exclusively used for mut - let (_, value_opt) = self.borrow_mut(arg, UsageKind::BorrowMut)?; + let Ok((_, value_opt)) = self.borrow_mut_impl(arg, None) else { + invariant_violation!("Should be able to borrow argument to restore it") + }; let old_value = value_opt.replace(value); assert_invariant!( old_value.is_none(), @@ -400,14 +386,9 @@ where } input_object_metadata.insert(object_metadata.id, object_metadata); }; - // gas can be unused let gas_id = gas.object_metadata.as_ref().unwrap().id; add_input_object_write(gas); - // all other inputs must be used at least once for input in inputs { - if input.inner.last_usage_kind.is_none() { - panic!("unused input") - } add_input_object_write(input) } // check for unused values @@ -420,20 +401,32 @@ where match value { None => (), Some(Value::Object(_)) => { - panic!("unused value without drop {i} {j}") + return Err(ExecutionErrorKind::UnusedValueWithoutDrop { + result_idx: i as u16, + secondary_idx: j as u16, + } + .into()) } Some(Value::Raw(RawValueType::Any, _)) => (), Some(Value::Raw(RawValueType::Loaded { abilities, .. }, _)) => { // - nothing to check for drop // - if it does not have drop, but has copy, - // the last usage must be a take/clone in order to "lie" and say that the + // the last usage must be by value in order to "lie" and say that the // last usage is actually a take instead of a clone // - Otherwise, an error if abilities.has_drop() { } else if abilities.has_copy() - && !matches!(last_usage_kind, Some(UsageKind::Take | UsageKind::Clone)) + && !matches!(last_usage_kind, Some(UsageKind::ByValue)) { - panic!("unused value without drop {i} {j}") + let msg = "The value has copy, but not drop. \ + Its last usage must be by-value so it can be taken."; + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::UnusedValueWithoutDrop { + result_idx: i as u16, + secondary_idx: j as u16, + }, + msg, + )); } } } @@ -604,7 +597,7 @@ where &mut self, arg: Argument, usage: UsageKind, - ) -> Result<(Option<&InputObjectMetadata>, &mut Option), ExecutionError> { + ) -> Result<(Option<&InputObjectMetadata>, &mut Option), CommandArgumentError> { self.borrow_mut_impl(arg, Some(usage)) } @@ -614,30 +607,33 @@ where &mut self, arg: Argument, update_last_usage: Option, - ) -> Result<(Option<&InputObjectMetadata>, &mut Option), ExecutionError> { + ) -> Result<(Option<&InputObjectMetadata>, &mut Option), CommandArgumentError> { let (metadata, result_value) = match arg { Argument::GasCoin => (self.gas.object_metadata.as_ref(), &mut self.gas.inner), Argument::Input(i) => { let Some(input_value) = self.inputs.get_mut(i as usize) else { - panic!("out of bounds") + return Err(CommandArgumentError::IndexOutOfBounds { idx: i }); }; (input_value.object_metadata.as_ref(), &mut input_value.inner) } Argument::Result(i) => { let Some(command_result) = self.results.get_mut(i as usize) else { - panic!("out of bounds") + return Err(CommandArgumentError::IndexOutOfBounds { idx: i }); }; if command_result.len() != 1 { - panic!("expected a single result") + return Err(CommandArgumentError::InvalidResultArity { result_idx: i }); } (None, &mut command_result[0]) } Argument::NestedResult(i, j) => { let Some(command_result) = self.results.get_mut(i as usize) else { - panic!("out of bounds") + return Err(CommandArgumentError::IndexOutOfBounds { idx: i }); }; let Some(result_value) = command_result.get_mut(j as usize) else { - panic!("out of bounds") + return Err(CommandArgumentError::SecondaryIndexOutOfBounds { + result_idx: i, + secondary_idx: j, + }); }; (None, result_value) } @@ -768,7 +764,10 @@ fn refund_max_gas_budget( .balance .value() .checked_add(gas_status.max_gax_budget_in_balance()) else { - panic!("coin overflow") + return Err(ExecutionError::new_with_source( + ExecutionErrorKind::TotalCoinBalanceOverflow, + "Gas coin too large after returning the max gas budget", + )); }; coin.balance = Balance::new(new_balance); *bytes = coin.to_bcs_bytes(); diff --git a/crates/sui-adapter/src/programmable_transactions/execution.rs b/crates/sui-adapter/src/programmable_transactions/execution.rs index d885057d71363..1cc3c0c557e38 100644 --- a/crates/sui-adapter/src/programmable_transactions/execution.rs +++ b/crates/sui-adapter/src/programmable_transactions/execution.rs @@ -29,7 +29,8 @@ use sui_types::{ gas::SuiGasStatus, id::UID, messages::{ - Argument, Command, EntryArgumentErrorKind, ProgrammableMoveCall, ProgrammableTransaction, + Argument, Command, CommandArgumentError, EntryArgumentErrorKind, ProgrammableMoveCall, + ProgrammableTransaction, }, SUI_FRAMEWORK_ADDRESS, }; @@ -126,8 +127,9 @@ fn execute_command>( None => { // empty args covered above let (idx, arg) = arg_iter.next().unwrap(); - let obj: ObjectValue = context.take_arg(CommandKind::MakeMoveVec, idx, arg)?; - res.extend(obj.to_bcs_bytes()); + let obj: ObjectValue = + context.by_value_arg(CommandKind::MakeMoveVec, idx, arg)?; + obj.write_bcs_bytes(&mut res); let tag = TypeTag::Struct(Box::new(obj.type_.clone())); (obj.used_in_non_entry_move_call, tag) } @@ -136,20 +138,12 @@ fn execute_command>( .session .load_type(&tag) .map_err(|e| context.convert_vm_error(e))?; - let elem_abilities = context - .session - .get_type_abilities(&elem_ty) - .map_err(|e| context.convert_vm_error(e))?; for (idx, arg) in arg_iter { - let value: Value = if elem_abilities.has_copy() { - context.clone_arg(idx, arg)? - } else { - context.take_arg(CommandKind::MakeMoveVec, idx, arg)? - }; + let value: Value = context.by_value_arg(CommandKind::MakeMoveVec, idx, arg)?; check_param_type(context, idx, &value, &elem_ty)?; used_in_non_entry_move_call = used_in_non_entry_move_call || value.was_used_in_non_entry_move_call(); - res.extend(value.to_bcs_bytes()); + value.write_bcs_bytes(&mut res); } let ty = Type::Vector(Box::new(elem_ty)); let abilities = context @@ -169,9 +163,10 @@ fn execute_command>( let objs: Vec = objs .into_iter() .enumerate() - .map(|(idx, arg)| context.take_arg(CommandKind::TransferObjects, idx, arg)) + .map(|(idx, arg)| context.by_value_arg(CommandKind::TransferObjects, idx, arg)) .collect::>()?; - let addr: SuiAddress = context.clone_arg(objs.len(), addr_arg)?; + let addr: SuiAddress = + context.by_value_arg(CommandKind::TransferObjects, objs.len(), addr_arg)?; for obj in objs { obj.ensure_public_transfer_eligible()?; context.transfer_object(obj, addr)?; @@ -181,9 +176,14 @@ fn execute_command>( Command::SplitCoin(coin_arg, amount_arg) => { let mut obj: ObjectValue = context.borrow_arg_mut(0, coin_arg)?; let ObjectContents::Coin(coin) = &mut obj.contents else { - panic!("not a coin") + let e = ExecutionErrorKind::command_argument_error( + CommandArgumentError::TypeMismatch, + 0, + ); + let msg = format!("Expected a coin but got an object of type {}", obj.type_); + return Err(ExecutionError::new_with_source(e, msg)); }; - let amount: u64 = context.clone_arg(1, amount_arg)?; + let amount: u64 = context.by_value_arg(CommandKind::SplitCoin, 1, amount_arg)?; let new_coin_id = context.fresh_id()?; let new_coin = coin.split_coin(amount, UID::new(new_coin_id))?; let coin_type = obj.type_.clone(); @@ -193,22 +193,43 @@ fn execute_command>( Command::MergeCoins(target_arg, coin_args) => { let mut target: ObjectValue = context.borrow_arg_mut(0, target_arg)?; let ObjectContents::Coin(target_coin) = &mut target.contents else { - panic!("not a coin") + let e = ExecutionErrorKind::command_argument_error( + CommandArgumentError::TypeMismatch, + 0, + ); + let msg = format!("Expected a coin but got an object of type {}", target.type_); + return Err(ExecutionError::new_with_source(e, msg)); }; let coins: Vec = coin_args .into_iter() .enumerate() - .map(|(idx, arg)| context.take_arg(CommandKind::MergeCoins, idx + 1, arg)) + .map(|(idx, arg)| context.by_value_arg(CommandKind::MergeCoins, idx + 1, arg)) .collect::>()?; - for coin in coins { + for (idx, coin) in coins.into_iter().enumerate() { + if target.type_ != coin.type_ { + let e = ExecutionErrorKind::command_argument_error( + CommandArgumentError::TypeMismatch, + (idx + 1) as u16, + ); + let msg = format!( + "Expected a coin of type {} but got an object of type {}", + target.type_, coin.type_ + ); + return Err(ExecutionError::new_with_source(e, msg)); + } let ObjectContents::Coin(Coin { id, balance }) = coin.contents else { - panic!("not a coin") + invariant_violation!( + "Target coin was a coin, and we already checked for the same type. \ + This should be a coin" + ); }; context.delete_id(*id.object_id())?; let Some(new_value) = target_coin.balance.value().checked_add(balance.value()) - else { - panic!("coin overflow") - }; + else { + return Err(ExecutionError::from_kind( + ExecutionErrorKind::TotalCoinBalanceOverflow, + )); + }; target_coin.balance = Balance::new(new_value); } context.restore_arg(target_arg, Value::Object(target))?; @@ -579,9 +600,12 @@ fn check_non_entry_signature>( signature .return_ .iter() - .map(|return_type| { + .enumerate() + .map(|(idx, return_type)| { if let Type::Reference(_) | Type::MutableReference(_) = return_type { - panic!("references not supported") + return Err(ExecutionError::from_kind( + ExecutionErrorKind::InvalidPublicFunctionReturnType { idx: idx as u16 }, + )); }; let abilities = context .session @@ -706,15 +730,7 @@ fn build_move_args>( } Type::Reference(inner) => (context.borrow_arg(idx, arg)?, inner), t => { - let abilities = context - .session - .get_type_abilities(t) - .map_err(|e| context.convert_vm_error(e))?; - let value = if abilities.has_copy() { - context.clone_arg(idx, arg)? - } else { - context.take_arg(command_kind, idx, arg)? - }; + let value = context.by_value_arg(command_kind, idx, arg)?; (value, t) } }; @@ -723,10 +739,17 @@ fn build_move_args>( FunctionKind::PrivateEntry | FunctionKind::Init ) && value.was_used_in_non_entry_move_call() { - panic!("private entry taint failed") + return Err(command_argument_error( + CommandArgumentError::InvalidArgumentToPrivateEntryFunction, + idx, + )); } check_param_type(context, idx, &value, non_ref_param_ty)?; - let bytes = value.to_bcs_bytes(); + let bytes = { + let mut v = vec![]; + value.write_bcs_bytes(&mut v); + v + }; // Any means this was just some bytes passed in as an argument (as opposed to being // generated from a Move function). Meaning we will need to run validation if matches!(value, Value::Raw(RawValueType::Any, _)) { @@ -758,13 +781,13 @@ fn check_param_type>( if !is_entry_primitive_type(context, param_ty)? { let msg = format!( "Non-primitive argument at index {}. If it is an object, it must be \ - populated by an object ID", + populated by an object", idx, ); return Err(ExecutionError::new_with_source( - ExecutionErrorKind::entry_argument_error( - idx as LocalIndex, - EntryArgumentErrorKind::UnsupportedPureArg, + ExecutionErrorKind::command_argument_error( + CommandArgumentError::InvalidUsageOfPureArg, + idx as u16, ), msg, )); @@ -785,7 +808,10 @@ fn check_param_type>( } }; if ty != param_ty { - panic!("type mismatch") + Err(command_argument_error( + CommandArgumentError::TypeMismatch, + idx, + )) } else { Ok(()) } diff --git a/crates/sui-adapter/src/programmable_transactions/types.rs b/crates/sui-adapter/src/programmable_transactions/types.rs index f224dfdebf88d..7c53512897996 100644 --- a/crates/sui-adapter/src/programmable_transactions/types.rs +++ b/crates/sui-adapter/src/programmable_transactions/types.rs @@ -15,6 +15,7 @@ use sui_types::{ base_types::{ObjectID, SequenceNumber, SuiAddress}, coin::Coin, error::{ExecutionError, ExecutionErrorKind}, + messages::CommandArgumentError, object::{Data, MoveObject, Object, Owner}, storage::{ChildObjectResolver, ObjectChange, ParentSync, Storage}, }; @@ -67,8 +68,7 @@ pub struct ResultValue { pub enum UsageKind { BorrowImm, BorrowMut, - Take, - Clone, + ByValue, } #[derive(Clone)] @@ -152,10 +152,10 @@ impl Value { } } - pub fn to_bcs_bytes(&self) -> Vec { + pub fn write_bcs_bytes(&self, buf: &mut Vec) { match self { - Value::Object(obj_value) => obj_value.to_bcs_bytes(), - Value::Raw(_, bytes) => bytes.clone(), + Value::Object(obj_value) => obj_value.write_bcs_bytes(buf), + Value::Raw(_, bytes) => buf.extend(bytes), } } @@ -233,46 +233,43 @@ impl ObjectValue { Ok(()) } - pub fn to_bcs_bytes(&self) -> Vec { + pub fn write_bcs_bytes(&self, buf: &mut Vec) { match &self.contents { - ObjectContents::Raw(bytes) => bytes.clone(), - ObjectContents::Coin(coin) => coin.to_bcs_bytes(), + ObjectContents::Raw(bytes) => buf.extend(bytes), + ObjectContents::Coin(coin) => buf.extend(coin.to_bcs_bytes()), } } } pub trait TryFromValue: Sized { - fn try_from_value(value: Value) -> Result; + fn try_from_value(value: Value) -> Result; } impl TryFromValue for Value { - fn try_from_value(value: Value) -> Result { + fn try_from_value(value: Value) -> Result { Ok(value) } } impl TryFromValue for ObjectValue { - fn try_from_value(value: Value) -> Result { + fn try_from_value(value: Value) -> Result { match value { Value::Object(o) => Ok(o), - Value::Raw(RawValueType::Any, _) => { - todo!("support this for dev inspect") - } - Value::Raw(RawValueType::Loaded { .. }, _) => { - panic!("not an object") - } + // TODO support Any for dev inspect + Value::Raw(RawValueType::Any, _) => Err(CommandArgumentError::TypeMismatch), + Value::Raw(RawValueType::Loaded { .. }, _) => Err(CommandArgumentError::TypeMismatch), } } } impl TryFromValue for SuiAddress { - fn try_from_value(value: Value) -> Result { + fn try_from_value(value: Value) -> Result { try_from_value_prim(&value, Type::Address) } } impl TryFromValue for u64 { - fn try_from_value(value: Value) -> Result { + fn try_from_value(value: Value) -> Result { try_from_value_prim(&value, Type::U64) } } @@ -280,25 +277,24 @@ impl TryFromValue for u64 { fn try_from_value_prim<'a, T: Deserialize<'a>>( value: &'a Value, expected_ty: Type, -) -> Result { +) -> Result { match value { - Value::Object(_) => { - panic!("expected non object") - } + Value::Object(_) => Err(CommandArgumentError::TypeMismatch), Value::Raw(RawValueType::Any, bytes) => { - let Ok(val) = bcs::from_bytes(bytes) else { - panic!("invalid pure arg") - }; - Ok(val) + bcs::from_bytes(bytes).map_err(|_| CommandArgumentError::InvalidBCSBytes) } Value::Raw(RawValueType::Loaded { ty, .. }, bytes) => { if ty == &expected_ty { - panic!("type mismatch") + return Err(CommandArgumentError::TypeMismatch); } - let Ok(res) = bcs::from_bytes(bytes) else { - panic!("invalid bytes for type") - }; - Ok(res) + bcs::from_bytes(bytes).map_err(|_| CommandArgumentError::InvalidBCSBytes) } } } + +pub fn command_argument_error(e: CommandArgumentError, arg_idx: usize) -> ExecutionError { + ExecutionError::from_kind(ExecutionErrorKind::command_argument_error( + e, + arg_idx as u16, + )) +} diff --git a/crates/sui-core/src/generate_format.rs b/crates/sui-core/src/generate_format.rs index 2fe419ba1f5d6..d596f02632a35 100644 --- a/crates/sui-core/src/generate_format.rs +++ b/crates/sui-core/src/generate_format.rs @@ -10,7 +10,6 @@ use move_core_types::{ use pretty_assertions::assert_str_eq; use serde_reflection::{Registry, Result, Samples, Tracer, TracerConfig}; use std::{fs::File, io::Write}; -use sui_types::crypto::Signer; use sui_types::{ base_types::{self, ObjectDigest, ObjectID, TransactionDigest, TransactionEffectsDigest}, crypto::{ @@ -25,6 +24,7 @@ use sui_types::{ object::{Data, Owner}, storage::DeleteKind, }; +use sui_types::{crypto::Signer, messages::CommandArgumentError}; use typed_store::rocks::TypedStoreError; fn get_registry() -> Result { @@ -92,6 +92,7 @@ fn get_registry() -> Result { tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; tracer.trace_type::(&samples)?; + tracer.trace_type::(&samples)?; tracer.registry() } diff --git a/crates/sui-core/tests/staged/sui.yaml b/crates/sui-core/tests/staged/sui.yaml index aeb49d4524f34..9ceb2e13540da 100644 --- a/crates/sui-core/tests/staged/sui.yaml +++ b/crates/sui-core/tests/staged/sui.yaml @@ -92,6 +92,39 @@ Command: TYPENAME: TypeTag - SEQ: TYPENAME: Argument +CommandArgumentError: + ENUM: + 0: + TypeMismatch: UNIT + 1: + InvalidBCSBytes: UNIT + 2: + InvalidUsageOfPureArg: UNIT + 3: + InvalidArgumentToPrivateEntryFunction: UNIT + 4: + IndexOutOfBounds: + STRUCT: + - idx: U16 + 5: + SecondaryIndexOutOfBounds: + STRUCT: + - result_idx: U16 + - secondary_idx: U16 + 6: + InvalidResultArity: + STRUCT: + - result_idx: U16 + 7: + InvalidGasCoinUsage: UNIT + 8: + InvalidUsageOfBorrowedValue: UNIT + 9: + InvalidUsageOfTakenValue: UNIT + 10: + InvalidObjectByValue: UNIT + 11: + InvalidObjectByMutRef: UNIT ConsensusCommitPrologue: STRUCT: - epoch: U64 @@ -259,6 +292,23 @@ ExecutionFailureStatus: TotalPaymentAmountOverflow: UNIT 36: TotalCoinBalanceOverflow: UNIT + 37: + CommandArgumentError: + STRUCT: + - arg_idx: U16 + - kind: + TYPENAME: CommandArgumentError + 38: + UnusedValueWithoutDrop: + STRUCT: + - result_idx: U16 + - secondary_idx: U16 + 39: + InvalidPublicFunctionReturnType: + STRUCT: + - idx: U16 + 40: + ArityMismatch: UNIT ExecutionStatus: ENUM: 0: diff --git a/crates/sui-types/src/messages.rs b/crates/sui-types/src/messages.rs index bc9a2ec670f37..7fcb56f07fd90 100644 --- a/crates/sui-types/src/messages.rs +++ b/crates/sui-types/src/messages.rs @@ -1,6 +1,7 @@ // Copyright (c) 2021, Facebook, Inc. and its affiliates // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 + use super::{base_types::*, committee::Committee, error::*, event::Event}; use crate::certificate_proof::CertificateProof; use crate::committee::{EpochId, ProtocolVersion, StakeUnit}; @@ -48,6 +49,7 @@ use std::{ use strum::IntoStaticStr; use sui_protocol_config::{ProtocolConfig, SupportedProtocolVersions}; use tap::Pipe; +use thiserror::Error; use tracing::debug; pub const DUMMY_GAS_PRICE: u64 = 1; @@ -2299,6 +2301,22 @@ pub enum ExecutionFailureStatus { TotalPaymentAmountOverflow, /// The total balance of coins is larger than the maximum value of u64. TotalCoinBalanceOverflow, + + // + // Programmable Transaction Errors + // + CommandArgumentError { + arg_idx: u16, + kind: CommandArgumentError, + }, + UnusedValueWithoutDrop { + result_idx: u16, + secondary_idx: u16, + }, + InvalidPublicFunctionReturnType { + idx: u16, + }, + ArityMismatch, // NOTE: if you want to add a new enum, // please add it at the end for Rust SDK backward compatibility. } @@ -2357,6 +2375,53 @@ pub struct InvalidSharedByValue { pub object: ObjectID, } +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Hash, Error)] +pub enum CommandArgumentError { + #[error("The type of the value does not match the expected type")] + TypeMismatch, + #[error("The argument cannot be deserialized into a value of the specified type")] + InvalidBCSBytes, + #[error("The argument cannot be instantiated from raw bytes")] + InvalidUsageOfPureArg, + #[error( + "Invalid argument to private entry function. \ + These functions cannot take arguments from other Move functions" + )] + InvalidArgumentToPrivateEntryFunction, + #[error("Out of bounds access to input or result vector {idx}")] + IndexOutOfBounds { idx: u16 }, + #[error( + "Out of bounds secondary access to result vector \ + {result_idx} at secondary index {secondary_idx}" + )] + SecondaryIndexOutOfBounds { result_idx: u16, secondary_idx: u16 }, + #[error( + "Invalid usage of result {result_idx}, \ + expected a single result but found multiple return values" + )] + InvalidResultArity { result_idx: u16 }, + #[error( + "Invalid taking of the Gas coin. \ + It can only be used by-value with TransferObjects" + )] + InvalidGasCoinUsage, + #[error( + "Invalid usage of borrowed value. \ + Mutably borrowed values require unique usage. \ + Immutably borrowed values cannot be taken or borrowed mutably" + )] + InvalidUsageOfBorrowedValue, + #[error( + "Invalid usage of already taken value. \ + There is now no value available at this location" + )] + InvalidUsageOfTakenValue, + #[error("Immutable and shared objects cannot be passed by-value.")] + InvalidObjectByValue, + #[error("Immutable objects cannot be passed by mutable reference, &mut.")] + InvalidObjectByMutRef, +} + impl ExecutionFailureStatus { pub fn entry_argument_error(argument_idx: LocalIndex, kind: EntryArgumentErrorKind) -> Self { EntryArgumentError { argument_idx, kind }.into() @@ -2380,6 +2445,10 @@ impl ExecutionFailureStatus { pub fn invalid_shared_by_value(object: ObjectID) -> Self { InvalidSharedByValue { object }.into() } + + pub fn command_argument_error(kind: CommandArgumentError, arg_idx: u16) -> Self { + Self::CommandArgumentError { arg_idx, kind } + } } impl Display for ExecutionFailureStatus { @@ -2392,11 +2461,8 @@ impl Display for ExecutionFailureStatus { ) } ExecutionFailureStatus::CoinTooLarge => { - write!( - f, - "Coin exceeds maximum value for a single coin" - ) - }, + write!(f, "Coin exceeds maximum value for a single coin") + } ExecutionFailureStatus::EmptyInputCoins => { write!(f, "Expected a non-empty list of input Coin objects") } @@ -2417,8 +2483,22 @@ impl Display for ExecutionFailureStatus { ExecutionFailureStatus::InvalidTransactionUpdate => { write!(f, "Invalid Transaction Update.") } - ExecutionFailureStatus::MoveObjectTooBig { object_size, max_object_size } => write!(f, "Move object with size {object_size} is larger than the maximum object size {max_object_size}"), - ExecutionFailureStatus::MovePackageTooBig { object_size, max_object_size } => write!(f, "Move package with size {object_size} is larger than the maximum object size {max_object_size}"), + ExecutionFailureStatus::MoveObjectTooBig { + object_size, + max_object_size, + } => write!( + f, + "Move object with size {object_size} is larger \ + than the maximum object size {max_object_size}" + ), + ExecutionFailureStatus::MovePackageTooBig { + object_size, + max_object_size, + } => write!( + f, + "Move package with size {object_size} is larger than the \ + maximum object size {max_object_size}" + ), ExecutionFailureStatus::FunctionNotFound => write!(f, "Function Not Found."), ExecutionFailureStatus::InvariantViolation => write!(f, "INVARIANT VIOLATION."), ExecutionFailureStatus::InvalidTransferObject => write!( @@ -2534,19 +2614,40 @@ impl Display for ExecutionFailureStatus { ), ExecutionFailureStatus::VMInvariantViolation => { write!(f, "MOVE VM INVARIANT VIOLATION.") - }, + } ExecutionFailureStatus::TotalPaymentAmountOverflow => { + write!(f, "The total amount of coins to be paid overflows of u64") + } + ExecutionFailureStatus::TotalCoinBalanceOverflow => { + write!(f, "The total balance of coins overflows u64") + } + ExecutionFailureStatus::CommandArgumentError { arg_idx, kind } => { + write!(f, "Invalid command argument at {arg_idx}. {kind}") + } + ExecutionFailureStatus::UnusedValueWithoutDrop { + result_idx, + secondary_idx, + } => { write!( f, - "The total amount of coins to be paid overflows of u64" + "Unused result without the drop ability. \ + Command result {result_idx}, return value {secondary_idx}" ) - }, - ExecutionFailureStatus::TotalCoinBalanceOverflow => { + } + ExecutionFailureStatus::InvalidPublicFunctionReturnType { idx } => { write!( f, - "The total balance of coins overflows u64" + "Invalid public Move function signature. \ + Unsupported return type for return value {idx}" ) - }, + } + ExecutionFailureStatus::ArityMismatch => { + write!( + f, + "Arity mismatch for Move function. \ + The number of arguments does not match the number of parameters" + ) + } } } }