thruput-sdk 0.58.0

Thruput smart-contract SDK for Rust (rv32 guest runtime and native test harness)
Documentation
//! System functions: non-negative integers
//!
//! At the start of a transaction, a storage location holds a non-negative-int
//! value if a previous transaction performed a "set" operation there, and the
//! most recent such operation placed a value of the non-negative-int storage
//! type. Otherwise, it does not contain a non-negative-int value.
//!
//! If the location does not contain a non-negative-int value, it may
//! nevertheless be set or incremented, after which it has a value in the
//! current transaction.  An increment operation behaves as if there had been a
//! previous value of zero.
//!
//! If the location _does_ have a non-negative-int value, it may be set,
//! incremented, or retrieved (by `non_negative_int_get`).
//!

use crate::exn::nnint_overflow;
use crate::sys_exn::nnint_internal as nnint;
use crate::types::StorageKey;

const SHARED: bool = false;
const EXCLUSIVE: bool = true;

/// Get the current value.
///
/// Throws an exception if the location does not contain a non-negative-int value.
///
pub fn non_negative_int_get(key: &StorageKey) -> i128 {
    use crate::re_throw::OrReThrow;
    non_negative_int_try_get(key).or_re_throw()
}

/// Get the current value, reporting failure as `Err` instead of aborting
/// (the `try_` law; see the [`exn`](mod@crate::exn) module docs).
///
/// The `Err` is exactly the exception [`non_negative_int_get`] would have
/// thrown. An empty location is a `LibraryExn::Syscall` for which
/// [`is_storage_missing`](crate::exn::LibraryExn::is_storage_missing) is
/// true — use [`none_if_missing`](crate::exn::NoneIfMissing::none_if_missing)
/// to treat just that case as an ordinary `None`. A value of the wrong
/// storage type or any other syscall failure is a `LibraryExn::Syscall` as
/// well; an overflowing `set + add` is `LibraryExn::NonNegativeIntOverflow`.
///
pub fn non_negative_int_try_get(
    key: &StorageKey,
) -> Result<i128, crate::exn::LibraryExn> {
    let (s, a) = nnint::try_get_values_force(key)?;
    s.checked_add(a)
        .ok_or(crate::exn::LibraryExn::NonNegativeIntOverflow)
}

/// Set the value.
///
/// (This clears the effects of any previous "increment" operations.)
///
/// This call always succeeds, but it puts constraints on the transaction,
/// described below.
///
/// A negative value is allowed: it sets the commit-time floor to that value.
/// Subsequent increments may keep the value negative (down to the value set)
/// without an exception, but they cannot decrement below it — the storage layer
/// enforces `set + add >= min(set, 0)` at commit.
///
/// If a non-negative value is set, the floor is `0`: increments that would make
/// the committed value negative at the end of the transaction cause it to
/// revert.
///
/// A set of concurrent transactions with the same explicitly-set value may
/// included in the same block.
///
pub fn non_negative_int_set(key: &StorageKey, set_value: i128) {
    nnint::set_add(key, Some(set_value), 0, SHARED);
}

/// Set the value exclusively.
///
/// This behaves like [`non_negative_int_set`], with the additional constraint
/// that, if a concurrent exclusive-set operation is performed by another
/// contract invocation, only one such invocation can be included in the same
/// block. The set is followed by a request for an exclusive lock, which later
/// writes to the key in this transaction keep.
///
pub fn non_negative_int_set_exclusive(key: &StorageKey, set_value: i128) {
    nnint::set_add(key, Some(set_value), 0, EXCLUSIVE);
}

/// Increment the value.
///
/// Throws an exception if the increments accumulated from previous calls would overflow.
///
/// If a "set" operation has been used previously in this transaction and the
/// value resulting from adding the accumulated increments `amount` is
/// negative, this throws an exception.
///
/// Otherwise, if the resulting value (the value at the end of the last
/// transaction plus the accumulated increments) is negative, this function
/// will not immediately throw.  But note the transaction constraints at
/// `non_negative_int_set`.
///
/// A set of concurrent transactions may be included in the same block if their
/// explicitly-set value is the same (see `non_negative_int_set`) and the sum
/// of that value and of all their increments is non-negative.
///
pub fn non_negative_int_inc(key: &StorageKey, amount: i128) {
    // Combine the existing "add" value with this additional increment
    let (s, a) = nnint::get_values(key);
    let add_value = a.checked_add(amount).unwrap_or_else(|| nnint_overflow());

    // Update the increment, keeping the "set" value (if any) as-is.
    nnint::set_add(key, s, add_value, SHARED);
}

/// Increment the value.
///
/// This behaves similarly to `non_negative_int_inc`, except that it throws if
/// the sum of the explicitly-set value and the accumulated increments
/// would overflow, or if it is negative.
///
pub fn non_negative_int_strict_inc(key: &StorageKey, amount: i128) {
    use crate::re_throw::OrReThrow;
    non_negative_int_try_strict_inc(key, amount).or_re_throw()
}

/// Increment the value, like [`non_negative_int_strict_inc`], but report
/// failure to the caller instead of throwing (the `try_` law; see the
/// [`exn`](mod@crate::exn) module docs), so a contract can raise a
/// domain-specific exception (e.g. an ERC20 "amount too large").
///
/// On `Err` the stored value is unmodified. The domain errors are
/// `LibraryExn::NonNegativeIntUnderflow` and
/// `LibraryExn::NonNegativeIntOverflow`; a wrong storage type at the key or
/// any other syscall failure surfaces as `LibraryExn::Syscall`. Uses a
/// single read-write pair, so checking before decrementing costs no extra
/// storage roundtrip over `non_negative_int_inc`.
pub fn non_negative_int_try_strict_inc(
    key: &StorageKey,
    amount: i128,
) -> Result<(), crate::exn::LibraryExn> {
    use crate::exn::LibraryExn;

    let (s, a) = match nnint::try_get_values_force(key) {
        Ok(values) => values,
        // A never-written location increments as if from zero.
        Err(e) if e.is_storage_missing() => (0, 0),
        Err(e) => return Err(e),
    };

    let add_value = a
        .checked_add(amount)
        .ok_or(LibraryExn::NonNegativeIntOverflow)?;
    let total = s
        .checked_add(add_value)
        .ok_or(LibraryExn::NonNegativeIntOverflow)?;

    if total < 0 {
        return Err(LibraryExn::NonNegativeIntUnderflow);
    }

    nnint::try_set_add(key, Some(s), add_value, SHARED)
}