use std::{cell::RefCell, convert::TryFrom, rc::Rc};
use thiserror::Error;
use casper_types::{
bytesrepr::FromBytes,
system::{mint, mint::Error as MintError},
AccessRights, CLType, CLTyped, CLValue, CLValueError, Key, RuntimeArgs, RuntimeFootprint,
StoredValue, StoredValueTypeMismatch, URef, U512,
};
use crate::{
global_state::{error::Error as GlobalStateError, state::StateReader},
tracking_copy::{TrackingCopy, TrackingCopyError, TrackingCopyExt},
};
#[derive(Clone, Error, Debug)]
pub enum BurnError {
#[error("Invalid key {0}")]
UnexpectedKeyVariant(Key),
#[error("{}", _0)]
TypeMismatch(StoredValueTypeMismatch),
#[error("Forged reference: {}", _0)]
ForgedReference(URef),
#[error("Invalid access rights: {}", required)]
InvalidAccess {
required: AccessRights,
},
#[error("{0}")]
CLValue(CLValueError),
#[error("Invalid purse")]
InvalidPurse,
#[error("Invalid argument")]
InvalidArgument,
#[error("Missing argument")]
MissingArgument,
#[error("Attempt to transfer amount 0")]
AttemptToBurnZero,
#[error("Invalid operation")]
InvalidOperation,
#[error("Either the source or the target must be an admin (private chain).")]
RestrictedBurnAttempted,
#[error("Unable to determine if the target of a transfer is an admin")]
UnableToVerifyTargetIsAdmin,
#[error("{0}")]
TrackingCopy(TrackingCopyError),
#[error("{0}")]
Mint(MintError),
}
impl From<GlobalStateError> for BurnError {
fn from(gse: GlobalStateError) -> Self {
BurnError::TrackingCopy(TrackingCopyError::Storage(gse))
}
}
impl From<TrackingCopyError> for BurnError {
fn from(tce: TrackingCopyError) -> Self {
BurnError::TrackingCopy(tce)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BurnArgs {
source: URef,
amount: U512,
}
impl BurnArgs {
pub fn new(source: URef, amount: U512) -> Self {
Self { source, amount }
}
pub fn source(&self) -> URef {
self.source
}
pub fn amount(&self) -> U512 {
self.amount
}
}
impl TryFrom<BurnArgs> for RuntimeArgs {
type Error = CLValueError;
fn try_from(burn_args: BurnArgs) -> Result<Self, Self::Error> {
let mut runtime_args = RuntimeArgs::new();
runtime_args.insert(mint::ARG_SOURCE, burn_args.source)?;
runtime_args.insert(mint::ARG_AMOUNT, burn_args.amount)?;
Ok(runtime_args)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BurnRuntimeArgsBuilder {
inner: RuntimeArgs,
}
impl BurnRuntimeArgsBuilder {
pub fn new(imputed_runtime_args: RuntimeArgs) -> BurnRuntimeArgsBuilder {
BurnRuntimeArgsBuilder {
inner: imputed_runtime_args,
}
}
fn purse_exists<R>(&self, uref: URef, tracking_copy: Rc<RefCell<TrackingCopy<R>>>) -> bool
where
R: StateReader<Key, StoredValue, Error = GlobalStateError>,
{
let key = match tracking_copy
.borrow_mut()
.get_purse_balance_key(uref.into())
{
Ok(key) => key,
Err(_) => return false,
};
tracking_copy
.borrow_mut()
.get_available_balance(key)
.is_ok()
}
fn resolve_source_uref<R>(
&self,
account: &RuntimeFootprint,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
) -> Result<URef, BurnError>
where
R: StateReader<Key, StoredValue, Error = GlobalStateError>,
{
let imputed_runtime_args = &self.inner;
let arg_name = mint::ARG_SOURCE;
let uref = match imputed_runtime_args.get(arg_name) {
Some(cl_value) if *cl_value.cl_type() == CLType::URef => {
self.map_cl_value::<URef>(cl_value)?
}
Some(cl_value) if *cl_value.cl_type() == CLType::Option(CLType::URef.into()) => {
let Some(uref): Option<URef> = self.map_cl_value(cl_value)? else {
return account.main_purse().ok_or(BurnError::InvalidOperation);
};
uref
}
Some(_) => return Err(BurnError::InvalidArgument),
None => return account.main_purse().ok_or(BurnError::InvalidOperation),
};
if account
.main_purse()
.ok_or(BurnError::InvalidOperation)?
.addr()
== uref.addr()
{
return Ok(uref);
}
let normalized_uref = Key::URef(uref).normalize();
let maybe_named_key = account
.named_keys()
.keys()
.find(|&named_key| named_key.normalize() == normalized_uref);
match maybe_named_key {
Some(Key::URef(found_uref)) => {
if found_uref.is_writeable() {
if !self.purse_exists(found_uref.to_owned(), tracking_copy) {
return Err(BurnError::InvalidPurse);
}
Ok(uref)
} else {
Err(BurnError::InvalidAccess {
required: AccessRights::WRITE,
})
}
}
Some(key) => Err(BurnError::TypeMismatch(StoredValueTypeMismatch::new(
"Key::URef".to_string(),
key.type_string(),
))),
None => Err(BurnError::ForgedReference(uref)),
}
}
fn resolve_amount(&self) -> Result<U512, BurnError> {
let imputed_runtime_args = &self.inner;
let amount = match imputed_runtime_args.get(mint::ARG_AMOUNT) {
Some(amount_value) if *amount_value.cl_type() == CLType::U512 => {
self.map_cl_value(amount_value)?
}
Some(amount_value) if *amount_value.cl_type() == CLType::U64 => {
let amount: u64 = self.map_cl_value(amount_value)?;
U512::from(amount)
}
Some(_) => return Err(BurnError::InvalidArgument),
None => return Err(BurnError::MissingArgument),
};
if amount.is_zero() {
return Err(BurnError::AttemptToBurnZero);
}
Ok(amount)
}
pub fn build<R>(
self,
from: &RuntimeFootprint,
tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
) -> Result<BurnArgs, BurnError>
where
R: StateReader<Key, StoredValue, Error = GlobalStateError>,
{
let source = self.resolve_source_uref(from, Rc::clone(&tracking_copy))?;
let amount = self.resolve_amount()?;
Ok(BurnArgs { source, amount })
}
fn map_cl_value<T: CLTyped + FromBytes>(&self, cl_value: &CLValue) -> Result<T, BurnError> {
cl_value.clone().into_t().map_err(BurnError::CLValue)
}
}