1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
// This file is part of Substrate.

// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Datatype for easy mutation of the extra "sidecar" data.

use super::*;

/// A mutator type allowing inspection and possible modification of the extra "sidecar" data.
///
/// This may be used as a `Deref` for the pallet's extra data. If mutated (using `DerefMut`), then
/// any uncommitted changes (see `commit` function) will be automatically committed to storage when
/// dropped. Changes, even after committed, may be reverted to their original values with the
/// `revert` function.
pub struct ExtraMutator<T: Config<I>, I: 'static = ()> {
	id: T::AssetId,
	who: T::AccountId,
	original: T::Extra,
	pending: Option<T::Extra>,
}

impl<T: Config<I>, I: 'static> Drop for ExtraMutator<T, I> {
	fn drop(&mut self) {
		debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account");
	}
}

impl<T: Config<I>, I: 'static> sp_std::ops::Deref for ExtraMutator<T, I> {
	type Target = T::Extra;
	fn deref(&self) -> &T::Extra {
		match self.pending {
			Some(ref value) => value,
			None => &self.original,
		}
	}
}

impl<T: Config<I>, I: 'static> sp_std::ops::DerefMut for ExtraMutator<T, I> {
	fn deref_mut(&mut self) -> &mut T::Extra {
		if self.pending.is_none() {
			self.pending = Some(self.original.clone());
		}
		self.pending.as_mut().unwrap()
	}
}

impl<T: Config<I>, I: 'static> ExtraMutator<T, I> {
	pub(super) fn maybe_new(
		id: T::AssetId,
		who: impl sp_std::borrow::Borrow<T::AccountId>,
	) -> Option<ExtraMutator<T, I>> {
		if Account::<T, I>::contains_key(id, who.borrow()) {
			Some(ExtraMutator::<T, I> {
				id,
				who: who.borrow().clone(),
				original: Account::<T, I>::get(id, who.borrow()).extra,
				pending: None,
			})
		} else {
			None
		}
	}

	/// Commit any changes to storage.
	pub fn commit(&mut self) -> Result<(), ()> {
		if let Some(extra) = self.pending.take() {
			Account::<T, I>::try_mutate_exists(self.id, self.who.borrow(), |maybe_account| {
				if let Some(ref mut account) = maybe_account {
					account.extra = extra;
					Ok(())
				} else {
					Err(())
				}
			})
		} else {
			Ok(())
		}
	}

	/// Revert any changes, even those already committed by `self` and drop self.
	pub fn revert(mut self) -> Result<(), ()> {
		self.pending = None;
		Account::<T, I>::try_mutate_exists(self.id, self.who.borrow(), |maybe_account| {
			if let Some(ref mut account) = maybe_account {
				account.extra = self.original.clone();
				Ok(())
			} else {
				Err(())
			}
		})
	}
}