sui_move_natives_v2/
transfer.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::object_runtime::{ObjectRuntime, TransferResult};
5use crate::{
6    get_receiver_object_id, get_tag_and_layouts, object_runtime::object_store::ObjectResult,
7    NativesCostTable,
8};
9use move_binary_format::errors::{PartialVMError, PartialVMResult};
10use move_core_types::{
11    account_address::AccountAddress, gas_algebra::InternalGas, language_storage::TypeTag,
12    vm_status::StatusCode,
13};
14use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
15use move_vm_types::{
16    loaded_data::runtime_types::Type, natives::function::NativeResult, pop_arg, values::Value,
17};
18use smallvec::smallvec;
19use std::collections::VecDeque;
20use sui_types::{
21    base_types::{MoveObjectType, ObjectID, SequenceNumber},
22    object::Owner,
23};
24
25const E_SHARED_NON_NEW_OBJECT: u64 = 0;
26const E_BCS_SERIALIZATION_FAILURE: u64 = 1;
27const E_RECEIVING_OBJECT_TYPE_MISMATCH: u64 = 2;
28// Represents both the case where the object does not exist and the case where the object is not
29// able to be accessed through the parent that is passed-in.
30const E_UNABLE_TO_RECEIVE_OBJECT: u64 = 3;
31
32#[derive(Clone, Debug)]
33pub struct TransferReceiveObjectInternalCostParams {
34    pub transfer_receive_object_internal_cost_base: InternalGas,
35}
36/***************************************************************************************************
37* native fun receive_object_internal
38* Implementation of the Move native function `receive_object_internal<T: key>(parent: &mut UID, rec: Receiver<T>): T`
39*   gas cost: transfer_receive_object_internal_cost_base |  covers various fixed costs in the oper
40**************************************************************************************************/
41
42pub fn receive_object_internal(
43    context: &mut NativeContext,
44    mut ty_args: Vec<Type>,
45    mut args: VecDeque<Value>,
46) -> PartialVMResult<NativeResult> {
47    debug_assert!(ty_args.len() == 1);
48    debug_assert!(args.len() == 3);
49    let transfer_receive_object_internal_cost_params = context
50        .extensions_mut()
51        .get::<NativesCostTable>()
52        .transfer_receive_object_internal_cost_params
53        .clone();
54    native_charge_gas_early_exit!(
55        context,
56        transfer_receive_object_internal_cost_params.transfer_receive_object_internal_cost_base
57    );
58    let child_ty = ty_args.pop().unwrap();
59    let child_receiver_sequence_number: SequenceNumber = pop_arg!(args, u64).into();
60    let child_receiver_object_id = args.pop_back().unwrap();
61    let parent = pop_arg!(args, AccountAddress).into();
62    assert!(args.is_empty());
63    let child_id: ObjectID = get_receiver_object_id(child_receiver_object_id.copy_value().unwrap())
64        .unwrap()
65        .value_as::<AccountAddress>()
66        .unwrap()
67        .into();
68    assert!(ty_args.is_empty());
69
70    let Some((tag, layout, annotated_layout)) = get_tag_and_layouts(context, &child_ty)? else {
71        return Ok(NativeResult::err(
72            context.gas_used(),
73            E_BCS_SERIALIZATION_FAILURE,
74        ));
75    };
76
77    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
78    let child = match object_runtime.receive_object(
79        parent,
80        child_id,
81        child_receiver_sequence_number,
82        &child_ty,
83        &layout,
84        &annotated_layout,
85        MoveObjectType::from(tag),
86    ) {
87        // NB: Loaded and doesn't exist and inauthenticated read should lead to the exact same error
88        Ok(None) => {
89            return Ok(NativeResult::err(
90                context.gas_used(),
91                E_UNABLE_TO_RECEIVE_OBJECT,
92            ))
93        }
94        Ok(Some(ObjectResult::Loaded(gv))) => gv,
95        Ok(Some(ObjectResult::MismatchedType)) => {
96            return Ok(NativeResult::err(
97                context.gas_used(),
98                E_RECEIVING_OBJECT_TYPE_MISMATCH,
99            ))
100        }
101        Err(x) => return Err(x),
102    };
103
104    Ok(NativeResult::ok(context.gas_used(), smallvec![child]))
105}
106
107#[derive(Clone, Debug)]
108pub struct TransferInternalCostParams {
109    pub transfer_transfer_internal_cost_base: InternalGas,
110}
111/***************************************************************************************************
112* native fun transfer_impl
113* Implementation of the Move native function `transfer_impl<T: key>(obj: T, recipient: address)`
114*   gas cost: transfer_transfer_internal_cost_base                  |  covers various fixed costs in the oper
115**************************************************************************************************/
116pub fn transfer_internal(
117    context: &mut NativeContext,
118    mut ty_args: Vec<Type>,
119    mut args: VecDeque<Value>,
120) -> PartialVMResult<NativeResult> {
121    debug_assert!(ty_args.len() == 1);
122    debug_assert!(args.len() == 2);
123
124    let transfer_transfer_internal_cost_params = context
125        .extensions_mut()
126        .get::<NativesCostTable>()
127        .transfer_transfer_internal_cost_params
128        .clone();
129
130    native_charge_gas_early_exit!(
131        context,
132        transfer_transfer_internal_cost_params.transfer_transfer_internal_cost_base
133    );
134
135    let ty = ty_args.pop().unwrap();
136    let recipient = pop_arg!(args, AccountAddress);
137    let obj = args.pop_back().unwrap();
138
139    let owner = Owner::AddressOwner(recipient.into());
140    object_runtime_transfer(context, owner, ty, obj)?;
141    let cost = context.gas_used();
142    Ok(NativeResult::ok(cost, smallvec![]))
143}
144
145#[derive(Clone, Debug)]
146pub struct TransferFreezeObjectCostParams {
147    pub transfer_freeze_object_cost_base: InternalGas,
148}
149/***************************************************************************************************
150* native fun freeze_object
151* Implementation of the Move native function `freeze_object<T: key>(obj: T)`
152*   gas cost: transfer_freeze_object_cost_base                  |  covers various fixed costs in the oper
153**************************************************************************************************/
154pub fn freeze_object(
155    context: &mut NativeContext,
156    mut ty_args: Vec<Type>,
157    mut args: VecDeque<Value>,
158) -> PartialVMResult<NativeResult> {
159    debug_assert!(ty_args.len() == 1);
160    debug_assert!(args.len() == 1);
161
162    let transfer_freeze_object_cost_params = context
163        .extensions_mut()
164        .get::<NativesCostTable>()
165        .transfer_freeze_object_cost_params
166        .clone();
167
168    native_charge_gas_early_exit!(
169        context,
170        transfer_freeze_object_cost_params.transfer_freeze_object_cost_base
171    );
172
173    let ty = ty_args.pop().unwrap();
174    let obj = args.pop_back().unwrap();
175
176    object_runtime_transfer(context, Owner::Immutable, ty, obj)?;
177
178    Ok(NativeResult::ok(context.gas_used(), smallvec![]))
179}
180
181#[derive(Clone, Debug)]
182pub struct TransferShareObjectCostParams {
183    pub transfer_share_object_cost_base: InternalGas,
184}
185/***************************************************************************************************
186* native fun share_object
187* Implementation of the Move native function `share_object<T: key>(obj: T)`
188*   gas cost: transfer_share_object_cost_base                  |  covers various fixed costs in the oper
189**************************************************************************************************/
190pub fn share_object(
191    context: &mut NativeContext,
192    mut ty_args: Vec<Type>,
193    mut args: VecDeque<Value>,
194) -> PartialVMResult<NativeResult> {
195    debug_assert!(ty_args.len() == 1);
196    debug_assert!(args.len() == 1);
197
198    let transfer_share_object_cost_params = context
199        .extensions_mut()
200        .get::<NativesCostTable>()
201        .transfer_share_object_cost_params
202        .clone();
203
204    native_charge_gas_early_exit!(
205        context,
206        transfer_share_object_cost_params.transfer_share_object_cost_base
207    );
208
209    let ty = ty_args.pop().unwrap();
210    let obj = args.pop_back().unwrap();
211    let transfer_result = object_runtime_transfer(
212        context,
213        // Dummy version, to be filled with the correct initial version when the effects of the
214        // transaction are written to storage.
215        Owner::Shared {
216            initial_shared_version: SequenceNumber::new(),
217        },
218        ty,
219        obj,
220    )?;
221    let cost = context.gas_used();
222    Ok(match transfer_result {
223        // New means the ID was created in this transaction
224        // SameOwner means the object was previously shared and was re-shared
225        TransferResult::New | TransferResult::SameOwner => NativeResult::ok(cost, smallvec![]),
226        TransferResult::OwnerChanged => NativeResult::err(cost, E_SHARED_NON_NEW_OBJECT),
227    })
228}
229
230fn object_runtime_transfer(
231    context: &mut NativeContext,
232    owner: Owner,
233    ty: Type,
234    obj: Value,
235) -> PartialVMResult<TransferResult> {
236    if !matches!(context.type_to_type_tag(&ty)?, TypeTag::Struct(_)) {
237        return Err(
238            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
239                .with_message("Sui verifier guarantees this is a struct".to_string()),
240        );
241    }
242
243    let obj_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
244    obj_runtime.transfer(owner, ty, obj)
245}