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#[derive(Clone, Error, Debug)]
18pub enum BurnError {
19 #[error("Invalid key {0}")]
21 UnexpectedKeyVariant(Key),
22 #[error("{}", _0)]
24 TypeMismatch(StoredValueTypeMismatch),
25 #[error("Forged reference: {}", _0)]
27 ForgedReference(URef),
28 #[error("Invalid access rights: {}", required)]
30 InvalidAccess {
31 required: AccessRights,
33 },
34 #[error("{0}")]
36 CLValue(CLValueError),
37 #[error("Invalid purse")]
39 InvalidPurse,
40 #[error("Invalid argument")]
42 InvalidArgument,
43 #[error("Missing argument")]
45 MissingArgument,
46 #[error("Attempt to transfer amount 0")]
48 AttemptToBurnZero,
49 #[error("Invalid operation")]
51 InvalidOperation,
52 #[error("Either the source or the target must be an admin (private chain).")]
54 RestrictedBurnAttempted,
55 #[error("Unable to determine if the target of a transfer is an admin")]
57 UnableToVerifyTargetIsAdmin,
58 #[error("{0}")]
60 TrackingCopy(TrackingCopyError),
61 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct BurnArgs {
83 source: URef,
84 amount: U512,
85}
86
87impl BurnArgs {
88 pub fn new(source: URef, amount: U512) -> Self {
90 Self { source, amount }
91 }
92
93 pub fn source(&self) -> URef {
95 self.source
96 }
97
98 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#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct BurnRuntimeArgsBuilder {
123 inner: RuntimeArgs,
124}
125
126impl BurnRuntimeArgsBuilder {
127 pub fn new(imputed_runtime_args: RuntimeArgs) -> BurnRuntimeArgsBuilder {
131 BurnRuntimeArgsBuilder {
132 inner: imputed_runtime_args,
133 }
134 }
135
136 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 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), };
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 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 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 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}