iota_move_natives_latest/
transfer.rs

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