casper_storage/system/
burn.rs

1use std::{cell::RefCell, convert::TryFrom, rc::Rc};
2use thiserror::Error;
3
4use casper_types::{
5    bytesrepr::FromBytes,
6    system::{mint, mint::Error as MintError},
7    AccessRights, CLType, CLTyped, CLValue, CLValueError, Key, RuntimeArgs, RuntimeFootprint,
8    StoredValue, StoredValueTypeMismatch, URef, U512,
9};
10
11use crate::{
12    global_state::{error::Error as GlobalStateError, state::StateReader},
13    tracking_copy::{TrackingCopy, TrackingCopyError, TrackingCopyExt},
14};
15
16/// Burn error.
17#[derive(Clone, Error, Debug)]
18pub enum BurnError {
19    /// Invalid key variant.
20    #[error("Invalid key {0}")]
21    UnexpectedKeyVariant(Key),
22    /// Type mismatch error.
23    #[error("{}", _0)]
24    TypeMismatch(StoredValueTypeMismatch),
25    /// Forged reference error.
26    #[error("Forged reference: {}", _0)]
27    ForgedReference(URef),
28    /// Invalid access.
29    #[error("Invalid access rights: {}", required)]
30    InvalidAccess {
31        /// Required access rights of the operation.
32        required: AccessRights,
33    },
34    /// Error converting a CLValue.
35    #[error("{0}")]
36    CLValue(CLValueError),
37    /// Invalid purse.
38    #[error("Invalid purse")]
39    InvalidPurse,
40    /// Invalid argument.
41    #[error("Invalid argument")]
42    InvalidArgument,
43    /// Missing argument.
44    #[error("Missing argument")]
45    MissingArgument,
46    /// Invalid purse.
47    #[error("Attempt to transfer amount 0")]
48    AttemptToBurnZero,
49    /// Invalid operation.
50    #[error("Invalid operation")]
51    InvalidOperation,
52    /// Disallowed transfer attempt (private chain).
53    #[error("Either the source or the target must be an admin (private chain).")]
54    RestrictedBurnAttempted,
55    /// Could not determine if target is an admin (private chain).
56    #[error("Unable to determine if the target of a transfer is an admin")]
57    UnableToVerifyTargetIsAdmin,
58    /// Tracking copy error.
59    #[error("{0}")]
60    TrackingCopy(TrackingCopyError),
61    /// Mint error.
62    #[error("{0}")]
63    Mint(MintError),
64}
65
66impl From<GlobalStateError> for BurnError {
67    fn from(gse: GlobalStateError) -> Self {
68        BurnError::TrackingCopy(TrackingCopyError::Storage(gse))
69    }
70}
71
72impl From<TrackingCopyError> for BurnError {
73    fn from(tce: TrackingCopyError) -> Self {
74        BurnError::TrackingCopy(tce)
75    }
76}
77
78/// Mint's burn arguments.
79///
80/// A struct has a benefit of static typing, which is helpful while resolving the arguments.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct BurnArgs {
83    source: URef,
84    amount: U512,
85}
86
87impl BurnArgs {
88    /// Creates new transfer arguments.
89    pub fn new(source: URef, amount: U512) -> Self {
90        Self { source, amount }
91    }
92
93    /// Returns `source` field.
94    pub fn source(&self) -> URef {
95        self.source
96    }
97
98    /// Returns `amount` field.
99    pub fn amount(&self) -> U512 {
100        self.amount
101    }
102}
103
104impl TryFrom<BurnArgs> for RuntimeArgs {
105    type Error = CLValueError;
106
107    fn try_from(burn_args: BurnArgs) -> Result<Self, Self::Error> {
108        let mut runtime_args = RuntimeArgs::new();
109
110        runtime_args.insert(mint::ARG_SOURCE, burn_args.source)?;
111        runtime_args.insert(mint::ARG_AMOUNT, burn_args.amount)?;
112
113        Ok(runtime_args)
114    }
115}
116
117/// State of a builder of a `BurnArgs`.
118///
119/// Purpose of this builder is to resolve native burn args into BurnTargetMode and a
120/// [`BurnArgs`] instance to execute actual token burn on the mint contract.
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct BurnRuntimeArgsBuilder {
123    inner: RuntimeArgs,
124}
125
126impl BurnRuntimeArgsBuilder {
127    /// Creates new burn args builder.
128    ///
129    /// Takes an incoming runtime args that represents native burn's arguments.
130    pub fn new(imputed_runtime_args: RuntimeArgs) -> BurnRuntimeArgsBuilder {
131        BurnRuntimeArgsBuilder {
132            inner: imputed_runtime_args,
133        }
134    }
135
136    /// Checks if a purse exists.
137    fn purse_exists<R>(&self, uref: URef, tracking_copy: Rc<RefCell<TrackingCopy<R>>>) -> bool
138    where
139        R: StateReader<Key, StoredValue, Error = GlobalStateError>,
140    {
141        let key = match tracking_copy
142            .borrow_mut()
143            .get_purse_balance_key(uref.into())
144        {
145            Ok(key) => key,
146            Err(_) => return false,
147        };
148        tracking_copy
149            .borrow_mut()
150            .get_available_balance(key)
151            .is_ok()
152    }
153
154    /// Resolves the source purse of the burn.
155    ///
156    /// User can optionally pass a "source" argument which should refer to an [`URef`] existing in
157    /// user's named keys. When the "source" argument is missing then user's main purse is assumed.
158    ///
159    /// Returns resolved [`URef`].
160    fn resolve_source_uref<R>(
161        &self,
162        account: &RuntimeFootprint,
163        tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
164    ) -> Result<URef, BurnError>
165    where
166        R: StateReader<Key, StoredValue, Error = GlobalStateError>,
167    {
168        let imputed_runtime_args = &self.inner;
169        let arg_name = mint::ARG_SOURCE;
170        let uref = match imputed_runtime_args.get(arg_name) {
171            Some(cl_value) if *cl_value.cl_type() == CLType::URef => {
172                self.map_cl_value::<URef>(cl_value)?
173            }
174            Some(cl_value) if *cl_value.cl_type() == CLType::Option(CLType::URef.into()) => {
175                let Some(uref): Option<URef> = self.map_cl_value(cl_value)? else {
176                    return account.main_purse().ok_or(BurnError::InvalidOperation);
177                };
178                uref
179            }
180            Some(_) => return Err(BurnError::InvalidArgument),
181            None => return account.main_purse().ok_or(BurnError::InvalidOperation), /* if no source purse passed use account
182                                                                                     * main purse */
183        };
184        if account
185            .main_purse()
186            .ok_or(BurnError::InvalidOperation)?
187            .addr()
188            == uref.addr()
189        {
190            return Ok(uref);
191        }
192
193        let normalized_uref = Key::URef(uref).normalize();
194        let maybe_named_key = account
195            .named_keys()
196            .keys()
197            .find(|&named_key| named_key.normalize() == normalized_uref);
198
199        match maybe_named_key {
200            Some(Key::URef(found_uref)) => {
201                if found_uref.is_writeable() {
202                    // it is a URef and caller has access but is it a purse URef?
203                    if !self.purse_exists(found_uref.to_owned(), tracking_copy) {
204                        return Err(BurnError::InvalidPurse);
205                    }
206
207                    Ok(uref)
208                } else {
209                    Err(BurnError::InvalidAccess {
210                        required: AccessRights::WRITE,
211                    })
212                }
213            }
214            Some(key) => Err(BurnError::TypeMismatch(StoredValueTypeMismatch::new(
215                "Key::URef".to_string(),
216                key.type_string(),
217            ))),
218            None => Err(BurnError::ForgedReference(uref)),
219        }
220    }
221
222    /// Resolves amount.
223    ///
224    /// User has to specify "amount" argument that could be either a [`U512`] or a u64.
225    fn resolve_amount(&self) -> Result<U512, BurnError> {
226        let imputed_runtime_args = &self.inner;
227
228        let amount = match imputed_runtime_args.get(mint::ARG_AMOUNT) {
229            Some(amount_value) if *amount_value.cl_type() == CLType::U512 => {
230                self.map_cl_value(amount_value)?
231            }
232            Some(amount_value) if *amount_value.cl_type() == CLType::U64 => {
233                let amount: u64 = self.map_cl_value(amount_value)?;
234                U512::from(amount)
235            }
236            Some(_) => return Err(BurnError::InvalidArgument),
237            None => return Err(BurnError::MissingArgument),
238        };
239
240        if amount.is_zero() {
241            return Err(BurnError::AttemptToBurnZero);
242        }
243
244        Ok(amount)
245    }
246
247    /// Creates new [`BurnArgs`] instance.
248    pub fn build<R>(
249        self,
250        from: &RuntimeFootprint,
251        tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
252    ) -> Result<BurnArgs, BurnError>
253    where
254        R: StateReader<Key, StoredValue, Error = GlobalStateError>,
255    {
256        let source = self.resolve_source_uref(from, Rc::clone(&tracking_copy))?;
257        let amount = self.resolve_amount()?;
258        Ok(BurnArgs { source, amount })
259    }
260
261    fn map_cl_value<T: CLTyped + FromBytes>(&self, cl_value: &CLValue) -> Result<T, BurnError> {
262        cl_value.clone().into_t().map_err(BurnError::CLValue)
263    }
264}