Skip to main content

sui_move_natives_latest/scratch/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4mod runtime;
5
6use runtime::AddResult;
7pub use runtime::ScratchRuntime;
8
9use crate::{NativesCostTable, get_extension, get_extension_mut};
10use move_binary_format::errors::{PartialVMError, PartialVMResult};
11use move_binary_format::{safe_assert, safe_assert_eq, safe_unwrap};
12use move_core_types::{
13    account_address::AccountAddress, gas_algebra::InternalGas, vm_status::StatusCode,
14};
15use move_vm_runtime::native_charge_gas_early_exit;
16use move_vm_runtime::natives::functions::NativeContext;
17use move_vm_runtime::{
18    execution::{Type, values::Value},
19    natives::functions::NativeResult,
20    pop_arg,
21    shared::views::{SizeConfig, ValueView},
22};
23use smallvec::smallvec;
24use std::collections::VecDeque;
25use sui_types::error::VMMemoryLimitExceededSubStatusCode;
26use tracing::instrument;
27
28// These must match the error constants declared in `sui::scratch`.
29const E_ENTRY_ALREADY_EXISTS: u64 = 0;
30const E_ENTRY_DOES_NOT_EXIST: u64 = 1;
31const E_ENTRY_TYPE_MISMATCH: u64 = 2;
32
33/// Abstract size of a scratch value, used to cost the copy performed by `read`.
34fn value_size(value: &Value) -> PartialVMResult<u64> {
35    Ok(value
36        .abstract_memory_size(&SizeConfig {
37            include_vector_size: true,
38            traverse_references: false,
39        })?
40        .into())
41}
42
43#[derive(Clone)]
44pub struct ScratchAddCostParams {
45    pub scratch_add_cost_base: Option<InternalGas>,
46}
47
48/***************************************************************************************************
49 * native fun add_impl
50 * throws `E_ENTRY_ALREADY_EXISTS` if there is already an entry for `key`, regardless of the type
51 * of `V`
52 * Implementation of the Move native function `add_impl<V: drop>(key: address, value: V)`
53 *   gas cost: scratch_add_cost_base                    | fixed cost, the value is moved into the
54 *                                                        store so its size is irrelevant
55 **************************************************************************************************/
56#[instrument(level = "trace", skip_all)]
57pub fn add_impl(
58    context: &mut NativeContext,
59    mut ty_args: Vec<Type>,
60    mut args: VecDeque<Value>,
61) -> PartialVMResult<NativeResult> {
62    safe_assert_eq!(ty_args.len(), 1);
63    safe_assert_eq!(args.len(), 2);
64
65    let scratch_add_cost_base = safe_unwrap!(
66        get_extension!(context, NativesCostTable)?
67            .scratch_add_cost_params
68            .scratch_add_cost_base
69    );
70    native_charge_gas_early_exit!(context, scratch_add_cost_base);
71
72    let value = safe_unwrap!(args.pop_back());
73    let key = pop_arg!(args, AccountAddress);
74    safe_assert!(args.is_empty());
75    let ty = safe_unwrap!(ty_args.pop());
76
77    let scratch_runtime: &mut ScratchRuntime = get_extension_mut!(context)?;
78    match scratch_runtime.add(key, ty, value) {
79        AddResult::Inserted => Ok(NativeResult::ok(context.gas_used(), smallvec![])),
80        AddResult::Duplicate => Ok(NativeResult::err(
81            context.gas_used(),
82            E_ENTRY_ALREADY_EXISTS,
83        )),
84        // Per-transaction capacity limit exceeded.
85        AddResult::LimitExceeded => Err(PartialVMError::new(StatusCode::MEMORY_LIMIT_EXCEEDED)
86            .with_message("Per-transaction scratch size limit was exceeded".to_string())
87            .with_sub_status(
88                VMMemoryLimitExceededSubStatusCode::SCRATCH_SIZE_LIMIT_EXCEEDED as u64,
89            )),
90    }
91}
92
93#[derive(Clone)]
94pub struct ScratchReadCostParams {
95    pub scratch_read_cost_base: Option<InternalGas>,
96    pub scratch_read_value_cost: Option<InternalGas>,
97}
98
99/***************************************************************************************************
100 * native fun read_impl
101 * throws `E_ENTRY_DOES_NOT_EXIST` if there is no entry for `key`
102 * or throws `E_ENTRY_TYPE_MISMATCH` if the entry's value is not of type `V`
103 * Implementation of the Move native function `read_impl<V: copy + drop>(key: address): V`
104 *   gas cost: scratch_read_cost_base                                     | fixed cost
105 *              + scratch_read_value_cost * size_of(value)       | covers copying the value out
106 **************************************************************************************************/
107#[instrument(level = "trace", skip_all)]
108pub fn read_impl(
109    context: &mut NativeContext,
110    mut ty_args: Vec<Type>,
111    mut args: VecDeque<Value>,
112) -> PartialVMResult<NativeResult> {
113    safe_assert_eq!(ty_args.len(), 1);
114    safe_assert_eq!(args.len(), 1);
115
116    let ScratchReadCostParams {
117        scratch_read_cost_base,
118        scratch_read_value_cost,
119    } = get_extension!(context, NativesCostTable)?
120        .scratch_read_cost_params
121        .clone();
122    let scratch_read_cost_base = safe_unwrap!(scratch_read_cost_base);
123    let scratch_read_value_cost = safe_unwrap!(scratch_read_value_cost);
124    native_charge_gas_early_exit!(context, scratch_read_cost_base);
125
126    let key = pop_arg!(args, AccountAddress);
127    safe_assert!(args.is_empty());
128    let ty = safe_unwrap!(ty_args.pop());
129
130    let scratch_runtime: &ScratchRuntime = get_extension!(context)?;
131    let entry = match scratch_runtime.get(&key) {
132        None => {
133            return Ok(NativeResult::err(
134                context.gas_used(),
135                E_ENTRY_DOES_NOT_EXIST,
136            ));
137        }
138        Some(entry) if entry.ty != ty => {
139            return Ok(NativeResult::err(context.gas_used(), E_ENTRY_TYPE_MISMATCH));
140        }
141        Some(entry) => entry,
142    };
143
144    native_charge_gas_early_exit!(
145        context,
146        scratch_read_value_cost * value_size(&entry.value)?.into()
147    );
148    let value = entry.value.copy_value();
149
150    Ok(NativeResult::ok(context.gas_used(), smallvec![value]))
151}
152
153#[derive(Clone)]
154pub struct ScratchRemoveCostParams {
155    pub scratch_remove_cost_base: Option<InternalGas>,
156}
157
158/***************************************************************************************************
159 * native fun remove_impl
160 * throws `E_ENTRY_DOES_NOT_EXIST` if there is no entry for `key`
161 * or throws `E_ENTRY_TYPE_MISMATCH` if the entry's value is not of type `V`
162 * Implementation of the Move native function `remove_impl<V: drop>(key: address): V`
163 *   gas cost: scratch_remove_cost_base                 | fixed cost, the value is moved out of the
164 *                                                        store so its size is irrelevant
165 **************************************************************************************************/
166#[instrument(level = "trace", skip_all)]
167pub fn remove_impl(
168    context: &mut NativeContext,
169    mut ty_args: Vec<Type>,
170    mut args: VecDeque<Value>,
171) -> PartialVMResult<NativeResult> {
172    safe_assert_eq!(ty_args.len(), 1);
173    safe_assert_eq!(args.len(), 1);
174
175    let scratch_remove_cost_base = safe_unwrap!(
176        get_extension!(context, NativesCostTable)?
177            .scratch_remove_cost_params
178            .scratch_remove_cost_base
179    );
180    native_charge_gas_early_exit!(context, scratch_remove_cost_base);
181
182    let key = pop_arg!(args, AccountAddress);
183    safe_assert!(args.is_empty());
184    let ty = safe_unwrap!(ty_args.pop());
185
186    let scratch_runtime: &mut ScratchRuntime = get_extension_mut!(context)?;
187    let Some(entry) = scratch_runtime.remove(&key) else {
188        return Ok(NativeResult::err(
189            context.gas_used(),
190            E_ENTRY_DOES_NOT_EXIST,
191        ));
192    };
193
194    if entry.ty != ty {
195        return Ok(NativeResult::err(context.gas_used(), E_ENTRY_TYPE_MISMATCH));
196    }
197
198    Ok(NativeResult::ok(context.gas_used(), smallvec![entry.value]))
199}
200
201#[derive(Clone)]
202pub struct ScratchExistsCostParams {
203    pub scratch_exists_cost_base: Option<InternalGas>,
204}
205
206/***************************************************************************************************
207 * native fun exists_impl
208 * Implementation of the Move native function `exists_impl(key: address): bool`
209 *   gas cost: scratch_exists_cost_base                 | fixed cost, this is a lookup
210 **************************************************************************************************/
211#[instrument(level = "trace", skip_all)]
212pub fn exists_impl(
213    context: &mut NativeContext,
214    ty_args: Vec<Type>,
215    mut args: VecDeque<Value>,
216) -> PartialVMResult<NativeResult> {
217    safe_assert!(ty_args.is_empty());
218    safe_assert_eq!(args.len(), 1);
219
220    let scratch_exists_cost_base = safe_unwrap!(
221        get_extension!(context, NativesCostTable)?
222            .scratch_exists_cost_params
223            .scratch_exists_cost_base
224    );
225    native_charge_gas_early_exit!(context, scratch_exists_cost_base);
226
227    let key = pop_arg!(args, AccountAddress);
228    let scratch_runtime: &ScratchRuntime = get_extension!(context)?;
229    let exists = scratch_runtime.contains(&key);
230    Ok(NativeResult::ok(
231        context.gas_used(),
232        smallvec![Value::bool(exists)],
233    ))
234}
235
236#[derive(Clone)]
237pub struct ScratchExistsWithTypeCostParams {
238    pub scratch_exists_with_type_cost_base: Option<InternalGas>,
239    pub scratch_exists_with_type_type_cost: Option<InternalGas>,
240}
241
242/***************************************************************************************************
243 * native fun exists_with_type_impl
244 * Implementation of the Move native function `exists_with_type_impl<V: drop>(key: address): bool`
245 *   gas cost: scratch_exists_with_type_cost_base                        | fixed cost
246 *              + scratch_exists_with_type_type_cost * size_of(V)        | covers operating on type `V`
247 **************************************************************************************************/
248#[instrument(level = "trace", skip_all)]
249pub fn exists_with_type_impl(
250    context: &mut NativeContext,
251    mut ty_args: Vec<Type>,
252    mut args: VecDeque<Value>,
253) -> PartialVMResult<NativeResult> {
254    safe_assert_eq!(ty_args.len(), 1);
255    safe_assert_eq!(args.len(), 1);
256
257    let ScratchExistsWithTypeCostParams {
258        scratch_exists_with_type_cost_base,
259        scratch_exists_with_type_type_cost,
260    } = get_extension!(context, NativesCostTable)?
261        .scratch_exists_with_type_cost_params
262        .clone();
263    let scratch_exists_with_type_cost_base = safe_unwrap!(scratch_exists_with_type_cost_base);
264    let scratch_exists_with_type_type_cost = safe_unwrap!(scratch_exists_with_type_type_cost);
265    native_charge_gas_early_exit!(context, scratch_exists_with_type_cost_base);
266
267    let key = pop_arg!(args, AccountAddress);
268    safe_assert!(args.is_empty());
269    let ty = safe_unwrap!(ty_args.pop());
270
271    native_charge_gas_early_exit!(
272        context,
273        scratch_exists_with_type_type_cost * u64::from(ty.size()?).into()
274    );
275
276    let scratch_runtime: &ScratchRuntime = get_extension!(context)?;
277    let exists = scratch_runtime
278        .get(&key)
279        .is_some_and(|entry| entry.ty == ty);
280    Ok(NativeResult::ok(
281        context.gas_used(),
282        smallvec![Value::bool(exists)],
283    ))
284}