sui_move_natives_latest/scratch/
mod.rs1mod 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
28const E_ENTRY_ALREADY_EXISTS: u64 = 0;
30const E_ENTRY_DOES_NOT_EXIST: u64 = 1;
31const E_ENTRY_TYPE_MISMATCH: u64 = 2;
32
33fn 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#[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 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#[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#[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#[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#[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}