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