Skip to main content

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