casper_contract/contract_api/
storage.rs

1//! Functions for accessing and mutating local and global state.
2
3use alloc::{
4    collections::{BTreeMap, BTreeSet},
5    format,
6    string::String,
7    vec,
8    vec::Vec,
9};
10use core::{convert::From, mem::MaybeUninit};
11
12use casper_types::{
13    addressable_entity::EntryPoints,
14    api_error,
15    bytesrepr::{self, FromBytes, ToBytes},
16    contract_messages::MessageTopicOperation,
17    contracts::{ContractHash, ContractPackageHash, ContractVersion, NamedKeys},
18    AccessRights, ApiError, CLTyped, CLValue, EntityVersion, HashAddr, Key, URef,
19    DICTIONARY_ITEM_KEY_MAX_LENGTH, UREF_SERIALIZED_LENGTH,
20};
21
22use crate::{
23    contract_api::{self, runtime, runtime::revert},
24    ext_ffi,
25    unwrap_or_revert::UnwrapOrRevert,
26};
27
28/// Reads value under `uref` in the global state.
29pub fn read<T: CLTyped + FromBytes>(uref: URef) -> Result<Option<T>, bytesrepr::Error> {
30    let key: Key = uref.into();
31    read_from_key(key)
32}
33
34/// Reads value under `key` in the global state.
35pub fn read_from_key<T: CLTyped + FromBytes>(key: Key) -> Result<Option<T>, bytesrepr::Error> {
36    let (key_ptr, key_size, _bytes) = contract_api::to_ptr(key);
37
38    let value_size = {
39        let mut value_size = MaybeUninit::uninit();
40        let ret = unsafe { ext_ffi::casper_read_value(key_ptr, key_size, value_size.as_mut_ptr()) };
41        match api_error::result_from(ret) {
42            Ok(_) => unsafe { value_size.assume_init() },
43            Err(ApiError::ValueNotFound) => return Ok(None),
44            Err(e) => runtime::revert(e),
45        }
46    };
47
48    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
49    Ok(Some(bytesrepr::deserialize(value_bytes)?))
50}
51
52/// Reads value under `uref` in the global state, reverts if value not found or is not `T`.
53pub fn read_or_revert<T: CLTyped + FromBytes>(uref: URef) -> T {
54    read(uref)
55        .unwrap_or_revert_with(ApiError::Read)
56        .unwrap_or_revert_with(ApiError::ValueNotFound)
57}
58
59/// Writes `value` under `uref` in the global state.
60pub fn write<T: CLTyped + ToBytes>(uref: URef, value: T) {
61    let key = Key::from(uref);
62    let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key);
63
64    let cl_value = CLValue::from_t(value).unwrap_or_revert();
65    let (cl_value_ptr, cl_value_size, _bytes2) = contract_api::to_ptr(cl_value);
66
67    unsafe {
68        ext_ffi::casper_write(key_ptr, key_size, cl_value_ptr, cl_value_size);
69    }
70}
71
72/// Adds `value` to the one currently under `uref` in the global state.
73pub fn add<T: CLTyped + ToBytes>(uref: URef, value: T) {
74    let key = Key::from(uref);
75    let (key_ptr, key_size, _bytes1) = contract_api::to_ptr(key);
76
77    let cl_value = CLValue::from_t(value).unwrap_or_revert();
78    let (cl_value_ptr, cl_value_size, _bytes2) = contract_api::to_ptr(cl_value);
79
80    unsafe {
81        // Could panic if `value` cannot be added to the given value in memory.
82        ext_ffi::casper_add(key_ptr, key_size, cl_value_ptr, cl_value_size);
83    }
84}
85
86/// Returns a new unforgeable pointer, where the value is initialized to `init`.
87pub fn new_uref<T: CLTyped + ToBytes>(init: T) -> URef {
88    let uref_non_null_ptr = contract_api::alloc_bytes(UREF_SERIALIZED_LENGTH);
89    let cl_value = CLValue::from_t(init).unwrap_or_revert();
90    let (cl_value_ptr, cl_value_size, _cl_value_bytes) = contract_api::to_ptr(cl_value);
91    let bytes = unsafe {
92        ext_ffi::casper_new_uref(uref_non_null_ptr.as_ptr(), cl_value_ptr, cl_value_size); // URef has `READ_ADD_WRITE`
93        Vec::from_raw_parts(
94            uref_non_null_ptr.as_ptr(),
95            UREF_SERIALIZED_LENGTH,
96            UREF_SERIALIZED_LENGTH,
97        )
98    };
99    bytesrepr::deserialize(bytes).unwrap_or_revert()
100}
101
102/// Create a new contract stored under a Key::Hash at version 1. You may upgrade this contract in
103/// the future; if you want a contract that is locked (i.e. cannot be upgraded) call
104/// `new_locked_contract` instead.
105/// if `named_keys` is provided, puts all of the included named keys into the newly created
106///     contract version's named keys.
107/// if `hash_name` is provided, puts Key::Hash(contract_package_hash) into the
108///     installing account's named keys under `hash_name`.
109/// if `uref_name` is provided, puts Key::URef(access_uref) into the installing account's named
110///     keys under `uref_name`
111pub fn new_contract(
112    entry_points: EntryPoints,
113    named_keys: Option<NamedKeys>,
114    hash_name: Option<String>,
115    uref_name: Option<String>,
116    message_topics: Option<BTreeMap<String, MessageTopicOperation>>,
117) -> (ContractHash, EntityVersion) {
118    create_contract(
119        entry_points,
120        named_keys,
121        hash_name,
122        uref_name,
123        message_topics,
124        false,
125    )
126}
127
128/// Create a locked contract stored under a Key::Hash, which can never be upgraded. This is an
129/// irreversible decision; for a contract that can be upgraded use `new_contract` instead.
130/// if `named_keys` is provided, puts all of the included named keys into the newly created
131///     contract version's named keys.
132/// if `hash_name` is provided, puts Key::Hash(contract_package_hash) into the
133///     installing account's named keys under `hash_name`.
134/// if `uref_name` is provided, puts Key::URef(access_uref) into the installing account's named
135///     keys under `uref_name`
136pub fn new_locked_contract(
137    entry_points: EntryPoints,
138    named_keys: Option<NamedKeys>,
139    hash_name: Option<String>,
140    uref_name: Option<String>,
141    message_topics: Option<BTreeMap<String, MessageTopicOperation>>,
142) -> (ContractHash, EntityVersion) {
143    create_contract(
144        entry_points,
145        named_keys,
146        hash_name,
147        uref_name,
148        message_topics,
149        true,
150    )
151}
152
153fn create_contract(
154    entry_points: EntryPoints,
155    named_keys: Option<NamedKeys>,
156    hash_name: Option<String>,
157    uref_name: Option<String>,
158    message_topics: Option<BTreeMap<String, MessageTopicOperation>>,
159    is_locked: bool,
160) -> (ContractHash, EntityVersion) {
161    let (contract_package_hash, access_uref) = create_contract_package(is_locked);
162
163    if let Some(hash_name) = hash_name {
164        runtime::put_key(&hash_name, Key::Hash(contract_package_hash.value()));
165    };
166
167    if let Some(uref_name) = uref_name {
168        runtime::put_key(&uref_name, access_uref.into());
169    };
170
171    let named_keys = named_keys.unwrap_or_default();
172
173    let message_topics = message_topics.unwrap_or_default();
174
175    add_contract_version(
176        contract_package_hash,
177        entry_points,
178        named_keys,
179        message_topics,
180    )
181}
182
183/// Create a new (versioned) contract stored under a Key::Hash. Initially there
184/// are no versions; a version must be added via `add_contract_version` before
185/// the contract can be executed.
186pub fn create_contract_package_at_hash() -> (ContractPackageHash, URef) {
187    create_contract_package(false)
188}
189
190fn create_contract_package(is_locked: bool) -> (ContractPackageHash, URef) {
191    let mut hash_addr: HashAddr = ContractPackageHash::default().value();
192    let mut access_addr = [0u8; 32];
193    unsafe {
194        ext_ffi::casper_create_contract_package_at_hash(
195            hash_addr.as_mut_ptr(),
196            access_addr.as_mut_ptr(),
197            is_locked,
198        );
199    }
200    let contract_package_hash: ContractPackageHash = hash_addr.into();
201    let access_uref = URef::new(access_addr, AccessRights::READ_ADD_WRITE);
202
203    (contract_package_hash, access_uref)
204}
205
206/// Create a new "user group" for a (versioned) contract. User groups associate
207/// a set of URefs with a label. Entry points on a contract can be given a list of
208/// labels they accept and the runtime will check that a URef from at least one
209/// of the allowed groups is present in the caller's context before
210/// execution. This allows access control for entry_points of a contract. This
211/// function returns the list of new URefs created for the group (the list will
212/// contain `num_new_urefs` elements).
213pub fn create_contract_user_group(
214    contract_package_hash: ContractPackageHash,
215    group_label: &str,
216    num_new_urefs: u8, // number of new urefs to populate the group with
217    existing_urefs: BTreeSet<URef>, // also include these existing urefs in the group
218) -> Result<Vec<URef>, ApiError> {
219    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
220        contract_api::to_ptr(contract_package_hash);
221    let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(group_label);
222    let (existing_urefs_ptr, existing_urefs_size, _bytes4) = contract_api::to_ptr(existing_urefs);
223
224    let value_size = {
225        let mut output_size = MaybeUninit::uninit();
226        let ret = unsafe {
227            ext_ffi::casper_create_contract_user_group(
228                contract_package_hash_ptr,
229                contract_package_hash_size,
230                label_ptr,
231                label_size,
232                num_new_urefs,
233                existing_urefs_ptr,
234                existing_urefs_size,
235                output_size.as_mut_ptr(),
236            )
237        };
238        api_error::result_from(ret).unwrap_or_revert();
239        unsafe { output_size.assume_init() }
240    };
241
242    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
243    Ok(bytesrepr::deserialize(value_bytes).unwrap_or_revert())
244}
245
246/// Extends specified group with a new `URef`.
247pub fn provision_contract_user_group_uref(
248    package_hash: ContractPackageHash,
249    label: &str,
250) -> Result<URef, ApiError> {
251    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
252        contract_api::to_ptr(package_hash);
253    let (label_ptr, label_size, _bytes2) = contract_api::to_ptr(label);
254    let value_size = {
255        let mut value_size = MaybeUninit::uninit();
256        let ret = unsafe {
257            ext_ffi::casper_provision_contract_user_group_uref(
258                contract_package_hash_ptr,
259                contract_package_hash_size,
260                label_ptr,
261                label_size,
262                value_size.as_mut_ptr(),
263            )
264        };
265        api_error::result_from(ret)?;
266        unsafe { value_size.assume_init() }
267    };
268    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
269    Ok(bytesrepr::deserialize(value_bytes).unwrap_or_revert())
270}
271
272/// Removes specified urefs from a named group.
273pub fn remove_contract_user_group_urefs(
274    package_hash: ContractPackageHash,
275    label: &str,
276    urefs: BTreeSet<URef>,
277) -> Result<(), ApiError> {
278    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
279        contract_api::to_ptr(package_hash);
280    let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(label);
281    let (urefs_ptr, urefs_size, _bytes4) = contract_api::to_ptr(urefs);
282    let ret = unsafe {
283        ext_ffi::casper_remove_contract_user_group_urefs(
284            contract_package_hash_ptr,
285            contract_package_hash_size,
286            label_ptr,
287            label_size,
288            urefs_ptr,
289            urefs_size,
290        )
291    };
292    api_error::result_from(ret)
293}
294
295/// Remove a named group from given contract.
296pub fn remove_contract_user_group(
297    package_hash: ContractPackageHash,
298    label: &str,
299) -> Result<(), ApiError> {
300    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
301        contract_api::to_ptr(package_hash);
302    let (label_ptr, label_size, _bytes3) = contract_api::to_ptr(label);
303    let ret = unsafe {
304        ext_ffi::casper_remove_contract_user_group(
305            contract_package_hash_ptr,
306            contract_package_hash_size,
307            label_ptr,
308            label_size,
309        )
310    };
311    api_error::result_from(ret)
312}
313
314/// Add version to existing Package.
315pub fn add_contract_version(
316    package_hash: ContractPackageHash,
317    entry_points: EntryPoints,
318    named_keys: NamedKeys,
319    message_topics: BTreeMap<String, MessageTopicOperation>,
320) -> (ContractHash, EntityVersion) {
321    // Retain the underscore as Wasm transpiliation requires it.
322    let (package_hash_ptr, package_hash_size, _package_hash_bytes) =
323        contract_api::to_ptr(package_hash);
324    let (entry_points_ptr, entry_points_size, _entry_point_bytes) =
325        contract_api::to_ptr(entry_points);
326    let (named_keys_ptr, named_keys_size, _named_keys_bytes) = contract_api::to_ptr(named_keys);
327    let (message_topics_ptr, message_topics_size, _message_topics) =
328        contract_api::to_ptr(message_topics);
329
330    let mut output_ptr = vec![0u8; 32];
331    // let mut total_bytes: usize = 0;
332
333    let mut entity_version: ContractVersion = 0;
334
335    let ret = unsafe {
336        ext_ffi::casper_add_contract_version_with_message_topics(
337            package_hash_ptr,
338            package_hash_size,
339            &mut entity_version as *mut ContractVersion, // Fixed width
340            entry_points_ptr,
341            entry_points_size,
342            named_keys_ptr,
343            named_keys_size,
344            message_topics_ptr,
345            message_topics_size,
346            output_ptr.as_mut_ptr(),
347            output_ptr.len(),
348            // &mut total_bytes as *mut usize,
349        )
350    };
351    match api_error::result_from(ret) {
352        Ok(_) => {}
353        Err(e) => revert(e),
354    }
355    // output_ptr.truncate(32usize);
356    let entity_hash: ContractHash = match bytesrepr::deserialize(output_ptr) {
357        Ok(hash) => hash,
358        Err(err) => panic!("{}", format!("{:?}", err)),
359    };
360    (entity_hash, entity_version)
361}
362
363/// Disables a specific version of a contract within the contract package identified by
364/// `contract_package_hash`. Once disabled, the specified version will no longer be
365/// callable by `call_versioned_contract`. Please note that the contract must have been
366/// previously created using `create_contract` or `create_contract_package_at_hash`.
367///
368/// # Arguments
369///
370/// * `contract_package_hash` - The hash of the contract package containing the version to be
371///   disabled.
372/// * `contract_hash` - The hash of the specific contract version to be disabled.
373///
374/// # Errors
375///
376/// Returns a `Result` indicating success or an `ApiError` if the operation fails.
377pub fn disable_contract_version(
378    contract_package_hash: ContractPackageHash,
379    contract_hash: ContractHash,
380) -> Result<(), ApiError> {
381    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
382        contract_api::to_ptr(contract_package_hash);
383    let (contract_hash_ptr, contract_hash_size, _bytes2) = contract_api::to_ptr(contract_hash);
384
385    let result = unsafe {
386        ext_ffi::casper_disable_contract_version(
387            contract_package_hash_ptr,
388            contract_package_hash_size,
389            contract_hash_ptr,
390            contract_hash_size,
391        )
392    };
393
394    api_error::result_from(result)
395}
396
397/// Enables a specific version of a contract from the contract package stored at the given hash.
398/// Once enabled, that version of the contract becomes callable again by `call_versioned_contract`.
399///
400/// # Arguments
401///
402/// * `contract_package_hash` - The hash of the contract package containing the desired version.
403/// * `contract_hash` - The hash of the specific contract version to be enabled.
404///
405/// # Errors
406///
407/// Returns a `Result` indicating success or an `ApiError` if the operation fails.
408pub fn enable_contract_version(
409    contract_package_hash: ContractPackageHash,
410    contract_hash: ContractHash,
411) -> Result<(), ApiError> {
412    let (contract_package_hash_ptr, contract_package_hash_size, _bytes1) =
413        contract_api::to_ptr(contract_package_hash);
414    let (contract_hash_ptr, contract_hash_size, _bytes2) = contract_api::to_ptr(contract_hash);
415
416    let result = unsafe {
417        ext_ffi::casper_enable_contract_version(
418            contract_package_hash_ptr,
419            contract_package_hash_size,
420            contract_hash_ptr,
421            contract_hash_size,
422        )
423    };
424
425    api_error::result_from(result)
426}
427
428/// Creates new [`URef`] that represents a seed for a dictionary partition of the global state and
429/// puts it under named keys.
430pub fn new_dictionary(dictionary_name: &str) -> Result<URef, ApiError> {
431    if dictionary_name.is_empty() || runtime::has_key(dictionary_name) {
432        return Err(ApiError::InvalidArgument);
433    }
434
435    let value_size = {
436        let mut value_size = MaybeUninit::uninit();
437        let ret = unsafe { ext_ffi::casper_new_dictionary(value_size.as_mut_ptr()) };
438        api_error::result_from(ret)?;
439        unsafe { value_size.assume_init() }
440    };
441    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
442    let uref: URef = bytesrepr::deserialize(value_bytes).unwrap_or_revert();
443    runtime::put_key(dictionary_name, Key::from(uref));
444    Ok(uref)
445}
446
447/// Retrieve `value` stored under `dictionary_item_key` in the dictionary accessed by
448/// `dictionary_seed_uref`.
449pub fn dictionary_get<V: CLTyped + FromBytes>(
450    dictionary_seed_uref: URef,
451    dictionary_item_key: &str,
452) -> Result<Option<V>, bytesrepr::Error> {
453    let (uref_ptr, uref_size, _bytes1) = contract_api::to_ptr(dictionary_seed_uref);
454    let (dictionary_item_key_ptr, dictionary_item_key_size) =
455        contract_api::dictionary_item_key_to_ptr(dictionary_item_key);
456
457    if dictionary_item_key_size > DICTIONARY_ITEM_KEY_MAX_LENGTH {
458        revert(ApiError::DictionaryItemKeyExceedsLength)
459    }
460
461    let value_size = {
462        let mut value_size = MaybeUninit::uninit();
463        let ret = unsafe {
464            ext_ffi::casper_dictionary_get(
465                uref_ptr,
466                uref_size,
467                dictionary_item_key_ptr,
468                dictionary_item_key_size,
469                value_size.as_mut_ptr(),
470            )
471        };
472        match api_error::result_from(ret) {
473            Ok(_) => unsafe { value_size.assume_init() },
474            Err(ApiError::ValueNotFound) => return Ok(None),
475            Err(e) => runtime::revert(e),
476        }
477    };
478
479    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
480    Ok(Some(bytesrepr::deserialize(value_bytes)?))
481}
482
483/// Writes `value` under `dictionary_item_key` in the dictionary accessed by `dictionary_seed_uref`.
484pub fn dictionary_put<V: CLTyped + ToBytes>(
485    dictionary_seed_uref: URef,
486    dictionary_item_key: &str,
487    value: V,
488) {
489    let (uref_ptr, uref_size, _bytes1) = contract_api::to_ptr(dictionary_seed_uref);
490    let (dictionary_item_key_ptr, dictionary_item_key_size) =
491        contract_api::dictionary_item_key_to_ptr(dictionary_item_key);
492
493    if dictionary_item_key_size > DICTIONARY_ITEM_KEY_MAX_LENGTH {
494        revert(ApiError::DictionaryItemKeyExceedsLength)
495    }
496
497    let cl_value = CLValue::from_t(value).unwrap_or_revert();
498    let (cl_value_ptr, cl_value_size, _bytes) = contract_api::to_ptr(cl_value);
499
500    let result = unsafe {
501        let ret = ext_ffi::casper_dictionary_put(
502            uref_ptr,
503            uref_size,
504            dictionary_item_key_ptr,
505            dictionary_item_key_size,
506            cl_value_ptr,
507            cl_value_size,
508        );
509        api_error::result_from(ret)
510    };
511
512    result.unwrap_or_revert()
513}
514
515/// Reads value under `dictionary_key` in the global state.
516pub fn dictionary_read<T: CLTyped + FromBytes>(dictionary_key: Key) -> Result<Option<T>, ApiError> {
517    if !dictionary_key.is_dictionary_key() {
518        return Err(ApiError::UnexpectedKeyVariant);
519    }
520
521    let (key_ptr, key_size, _bytes) = contract_api::to_ptr(dictionary_key);
522
523    let value_size = {
524        let mut value_size = MaybeUninit::uninit();
525        let ret =
526            unsafe { ext_ffi::casper_dictionary_read(key_ptr, key_size, value_size.as_mut_ptr()) };
527        match api_error::result_from(ret) {
528            Ok(_) => unsafe { value_size.assume_init() },
529            Err(ApiError::ValueNotFound) => return Ok(None),
530            Err(e) => runtime::revert(e),
531        }
532    };
533
534    let value_bytes = runtime::read_host_buffer(value_size).unwrap_or_revert();
535    Ok(Some(bytesrepr::deserialize(value_bytes)?))
536}
537
538fn get_named_uref(name: &str) -> URef {
539    match runtime::get_key(name).unwrap_or_revert_with(ApiError::GetKey) {
540        Key::URef(uref) => uref,
541        _ => revert(ApiError::UnexpectedKeyVariant),
542    }
543}
544
545/// Gets a value out of a named dictionary.
546pub fn named_dictionary_get<V: CLTyped + FromBytes>(
547    dictionary_name: &str,
548    dictionary_item_key: &str,
549) -> Result<Option<V>, bytesrepr::Error> {
550    dictionary_get(get_named_uref(dictionary_name), dictionary_item_key)
551}
552
553/// Writes a value in a named dictionary.
554pub fn named_dictionary_put<V: CLTyped + ToBytes>(
555    dictionary_name: &str,
556    dictionary_item_key: &str,
557    value: V,
558) {
559    dictionary_put(get_named_uref(dictionary_name), dictionary_item_key, value)
560}