1use std::{cell::RefCell, convert::TryFrom, rc::Rc};
2use thiserror::Error;
3
4use casper_types::{
5 account::AccountHash,
6 bytesrepr::FromBytes,
7 system::{mint, mint::Error as MintError},
8 AccessRights, CLType, CLTyped, CLValue, CLValueError, Key, ProtocolVersion, RuntimeArgs,
9 RuntimeFootprint, StoredValue, StoredValueTypeMismatch, URef, U512,
10};
11
12use crate::{
13 global_state::{error::Error as GlobalStateError, state::StateReader},
14 tracking_copy::{TrackingCopy, TrackingCopyEntityExt, TrackingCopyError, TrackingCopyExt},
15};
16
17#[derive(Clone, Error, Debug)]
19pub enum TransferError {
20 #[error("Invalid key {0}")]
22 UnexpectedKeyVariant(Key),
23 #[error("{}", _0)]
25 TypeMismatch(StoredValueTypeMismatch),
26 #[error("Forged reference: {}", _0)]
28 ForgedReference(URef),
29 #[error("Invalid access rights: {}", required)]
31 InvalidAccess {
32 required: AccessRights,
34 },
35 #[error("{0}")]
37 CLValue(CLValueError),
38 #[error("Invalid purse")]
40 InvalidPurse,
41 #[error("Invalid argument")]
43 InvalidArgument,
44 #[error("Missing argument")]
46 MissingArgument,
47 #[error("Attempt to transfer amount 0")]
49 AttemptToTransferZero,
50 #[error("Invalid operation")]
52 InvalidOperation,
53 #[error("Either the source or the target must be an admin (private chain).")]
55 RestrictedTransferAttempted,
56 #[error("Unable to determine if the target of a transfer is an admin")]
58 UnableToVerifyTargetIsAdmin,
59 #[error("{0}")]
61 TrackingCopy(TrackingCopyError),
62 #[error("{0}")]
64 Mint(MintError),
65}
66
67impl From<GlobalStateError> for TransferError {
68 fn from(gse: GlobalStateError) -> Self {
69 TransferError::TrackingCopy(TrackingCopyError::Storage(gse))
70 }
71}
72
73impl From<TrackingCopyError> for TransferError {
74 fn from(tce: TrackingCopyError) -> Self {
75 TransferError::TrackingCopy(tce)
76 }
77}
78
79#[derive(Copy, Clone, Debug, PartialEq)]
82pub enum TransferTargetMode {
83 ExistingAccount {
85 target_account_hash: AccountHash,
87 main_purse: URef,
89 },
90 PurseExists {
92 target_account_hash: Option<AccountHash>,
94 purse_uref: URef,
96 },
97 CreateAccount(AccountHash),
99}
100
101impl TransferTargetMode {
102 pub fn target_account_hash(&self) -> Option<AccountHash> {
104 match self {
105 TransferTargetMode::PurseExists {
106 target_account_hash,
107 ..
108 } => *target_account_hash,
109 TransferTargetMode::ExistingAccount {
110 target_account_hash,
111 ..
112 } => Some(*target_account_hash),
113 TransferTargetMode::CreateAccount(target_account_hash) => Some(*target_account_hash),
114 }
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub struct TransferArgs {
123 to: Option<AccountHash>,
124 source: URef,
125 target: URef,
126 amount: U512,
127 arg_id: Option<u64>,
128}
129
130impl TransferArgs {
131 pub fn new(
133 to: Option<AccountHash>,
134 source: URef,
135 target: URef,
136 amount: U512,
137 arg_id: Option<u64>,
138 ) -> Self {
139 Self {
140 to,
141 source,
142 target,
143 amount,
144 arg_id,
145 }
146 }
147
148 pub fn to(&self) -> Option<AccountHash> {
150 self.to
151 }
152
153 pub fn source(&self) -> URef {
155 self.source
156 }
157
158 pub fn target(&self) -> URef {
160 self.target
161 }
162
163 pub fn amount(&self) -> U512 {
165 self.amount
166 }
167
168 pub fn arg_id(&self) -> Option<u64> {
170 self.arg_id
171 }
172}
173
174impl TryFrom<TransferArgs> for RuntimeArgs {
175 type Error = CLValueError;
176
177 fn try_from(transfer_args: TransferArgs) -> Result<Self, Self::Error> {
178 let mut runtime_args = RuntimeArgs::new();
179
180 runtime_args.insert(mint::ARG_TO, transfer_args.to)?;
181 runtime_args.insert(mint::ARG_SOURCE, transfer_args.source)?;
182 runtime_args.insert(mint::ARG_TARGET, transfer_args.target)?;
183 runtime_args.insert(mint::ARG_AMOUNT, transfer_args.amount)?;
184 runtime_args.insert(mint::ARG_ID, transfer_args.arg_id)?;
185
186 Ok(runtime_args)
187 }
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct TransferRuntimeArgsBuilder {
196 inner: RuntimeArgs,
197}
198
199impl TransferRuntimeArgsBuilder {
200 pub fn new(imputed_runtime_args: RuntimeArgs) -> TransferRuntimeArgsBuilder {
204 TransferRuntimeArgsBuilder {
205 inner: imputed_runtime_args,
206 }
207 }
208
209 fn purse_exists<R>(&self, uref: URef, tracking_copy: Rc<RefCell<TrackingCopy<R>>>) -> bool
211 where
212 R: StateReader<Key, StoredValue, Error = GlobalStateError>,
213 {
214 let key = match tracking_copy
215 .borrow_mut()
216 .get_purse_balance_key(uref.into())
217 {
218 Ok(key) => key,
219 Err(_) => return false,
220 };
221 tracking_copy
222 .borrow_mut()
223 .get_available_balance(key)
224 .is_ok()
225 }
226
227 fn resolve_source_uref<R>(
234 &self,
235 account: &RuntimeFootprint,
236 tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
237 ) -> Result<URef, TransferError>
238 where
239 R: StateReader<Key, StoredValue, Error = GlobalStateError>,
240 {
241 let imputed_runtime_args = &self.inner;
242 let arg_name = mint::ARG_SOURCE;
243 let uref = match imputed_runtime_args.get(arg_name) {
244 Some(cl_value) if *cl_value.cl_type() == CLType::URef => {
245 self.map_cl_value::<URef>(cl_value)?
246 }
247 Some(cl_value) if *cl_value.cl_type() == CLType::Option(CLType::URef.into()) => {
248 let Some(uref): Option<URef> = self.map_cl_value(cl_value)? else {
249 return account.main_purse().ok_or(TransferError::InvalidOperation);
250 };
251 uref
252 }
253 Some(_) => return Err(TransferError::InvalidArgument),
254 None => return account.main_purse().ok_or(TransferError::InvalidOperation), };
257 if account
258 .main_purse()
259 .ok_or(TransferError::InvalidOperation)?
260 .addr()
261 == uref.addr()
262 {
263 return Ok(uref);
264 }
265
266 let normalized_uref = Key::URef(uref).normalize();
267 let maybe_named_key = account
268 .named_keys()
269 .keys()
270 .find(|&named_key| named_key.normalize() == normalized_uref);
271
272 match maybe_named_key {
273 Some(Key::URef(found_uref)) => {
274 if found_uref.is_writeable() {
275 if !self.purse_exists(found_uref.to_owned(), tracking_copy) {
277 return Err(TransferError::InvalidPurse);
278 }
279
280 Ok(uref)
281 } else {
282 Err(TransferError::InvalidAccess {
283 required: AccessRights::WRITE,
284 })
285 }
286 }
287 Some(key) => Err(TransferError::TypeMismatch(StoredValueTypeMismatch::new(
288 "Key::URef".to_string(),
289 key.type_string(),
290 ))),
291 None => Err(TransferError::ForgedReference(uref)),
292 }
293 }
294
295 pub fn resolve_transfer_target_mode<R>(
308 &mut self,
309 protocol_version: ProtocolVersion,
310 tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
311 ) -> Result<TransferTargetMode, TransferError>
312 where
313 R: StateReader<Key, StoredValue, Error = GlobalStateError>,
314 {
315 let imputed_runtime_args = &self.inner;
316 let to_name = mint::ARG_TO;
317
318 let target_account_hash = match imputed_runtime_args.get(to_name) {
319 Some(cl_value)
320 if *cl_value.cl_type() == CLType::Option(Box::new(CLType::ByteArray(32))) =>
321 {
322 let to: Option<AccountHash> = self.map_cl_value(cl_value)?;
323 to
324 }
325 Some(_) | None => None,
326 };
327
328 let target_name = mint::ARG_TARGET;
329 let account_hash = match imputed_runtime_args.get(target_name) {
330 Some(cl_value) if *cl_value.cl_type() == CLType::URef => {
331 let purse_uref = self.map_cl_value(cl_value)?;
332
333 if !self.purse_exists(purse_uref, tracking_copy) {
334 return Err(TransferError::InvalidPurse);
335 }
336
337 return Ok(TransferTargetMode::PurseExists {
338 purse_uref,
339 target_account_hash,
340 });
341 }
342 Some(cl_value) if *cl_value.cl_type() == CLType::ByteArray(32) => {
343 self.map_cl_value(cl_value)?
344 }
345 Some(cl_value) if *cl_value.cl_type() == CLType::Key => {
346 let account_key: Key = self.map_cl_value(cl_value)?;
347 let account_hash: AccountHash = account_key
348 .into_account()
349 .ok_or(TransferError::UnexpectedKeyVariant(account_key))?;
350 account_hash
351 }
352 Some(cl_value) if *cl_value.cl_type() == CLType::PublicKey => {
353 let public_key = self.map_cl_value(cl_value)?;
354 AccountHash::from(&public_key)
355 }
356 Some(_) => return Err(TransferError::InvalidArgument),
357 None => return Err(TransferError::MissingArgument),
358 };
359
360 match tracking_copy
361 .borrow_mut()
362 .runtime_footprint_by_account_hash(protocol_version, account_hash)
363 {
364 Ok((_, entity)) => {
365 let main_purse_addable = entity
366 .main_purse()
367 .ok_or(TransferError::InvalidPurse)?
368 .with_access_rights(AccessRights::ADD);
369 Ok(TransferTargetMode::ExistingAccount {
370 target_account_hash: account_hash,
371 main_purse: main_purse_addable,
372 })
373 }
374 Err(_) => Ok(TransferTargetMode::CreateAccount(account_hash)),
375 }
376 }
377
378 fn resolve_amount(&self) -> Result<U512, TransferError> {
382 let imputed_runtime_args = &self.inner;
383
384 let amount = match imputed_runtime_args.get(mint::ARG_AMOUNT) {
385 Some(amount_value) if *amount_value.cl_type() == CLType::U512 => {
386 self.map_cl_value(amount_value)?
387 }
388 Some(amount_value) if *amount_value.cl_type() == CLType::U64 => {
389 let amount: u64 = self.map_cl_value(amount_value)?;
390 U512::from(amount)
391 }
392 Some(_) => return Err(TransferError::InvalidArgument),
393 None => return Err(TransferError::MissingArgument),
394 };
395
396 if amount.is_zero() {
397 return Err(TransferError::AttemptToTransferZero);
398 }
399
400 Ok(amount)
401 }
402
403 fn resolve_id(&self) -> Result<Option<u64>, TransferError> {
404 let id: Option<u64> = if let Some(id_value) = self.inner.get(mint::ARG_ID) {
405 self.map_cl_value(id_value)?
406 } else {
407 None
408 };
409 Ok(id)
410 }
411
412 pub fn build<R>(
414 mut self,
415 from: &RuntimeFootprint,
416 protocol_version: ProtocolVersion,
417 tracking_copy: Rc<RefCell<TrackingCopy<R>>>,
418 ) -> Result<TransferArgs, TransferError>
419 where
420 R: StateReader<Key, StoredValue, Error = GlobalStateError>,
421 {
422 let (to, target) = match self
423 .resolve_transfer_target_mode(protocol_version, Rc::clone(&tracking_copy))?
424 {
425 TransferTargetMode::ExistingAccount {
426 main_purse: purse_uref,
427 target_account_hash: target_account,
428 } => (Some(target_account), purse_uref),
429 TransferTargetMode::PurseExists {
430 target_account_hash,
431 purse_uref,
432 } => (target_account_hash, purse_uref),
433 TransferTargetMode::CreateAccount(_) => {
434 return Err(TransferError::InvalidOperation);
438 }
439 };
440
441 let source = self.resolve_source_uref(from, Rc::clone(&tracking_copy))?;
442
443 if source.addr() == target.addr() {
444 return Err(TransferError::InvalidPurse);
445 }
446
447 let amount = self.resolve_amount()?;
448
449 let arg_id = self.resolve_id()?;
450
451 Ok(TransferArgs {
452 to,
453 source,
454 target,
455 amount,
456 arg_id,
457 })
458 }
459
460 fn map_cl_value<T: CLTyped + FromBytes>(&self, cl_value: &CLValue) -> Result<T, TransferError> {
461 cl_value.clone().into_t().map_err(TransferError::CLValue)
462 }
463}