diff --git a/core/primitives-core/src/account.rs b/core/primitives-core/src/account.rs index afb7581a649..5e62b34a4f6 100644 --- a/core/primitives-core/src/account.rs +++ b/core/primitives-core/src/account.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] use crate::checked_feature; use crate::hash::CryptoHash; use crate::serialize::dec_format; @@ -17,56 +16,75 @@ use std::io; Clone, Copy, Debug, - Default, serde::Serialize, serde::Deserialize, ProtocolSchema, + Default, )] pub enum AccountVersion { - #[cfg_attr(not(feature = "protocol_feature_nonrefundable_transfer_nep491"), default)] - V1, #[default] - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] + V1, V2, } -impl TryFrom for AccountVersion { - type Error = (); +/// Per account information stored in the state. +/// When introducing new version: +/// - introduce new AccountV[NewVersion] struct +/// - add new Account enum option V[NewVersion](AccountV[NewVersion]) +/// - add new BorshVersionedAccount enum option V[NewVersion](AccountV[NewVersion]) +/// - update SerdeAccount with newly added fields +/// - update serde ser/deser to properly handle conversions +#[derive(PartialEq, Eq, Debug, Clone, ProtocolSchema)] +pub enum Account { + V1(AccountV1), + V2(AccountV2), +} - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(AccountVersion::V1), - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - 2 => Ok(AccountVersion::V2), - _ => Err(()), - } - } +// Original representation of the account. +#[derive( + BorshSerialize, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + Debug, + Clone, + ProtocolSchema, +)] +pub struct AccountV1 { + /// The total not locked tokens. + amount: Balance, + /// The amount locked due to staking. + locked: Balance, + /// Hash of the code stored in the storage for this account. + code_hash: CryptoHash, + /// Storage used by the given account, includes account id, this struct, access keys and other data. + storage_usage: StorageUsage, } -/// Per account information stored in the state. -#[cfg_attr( - not(feature = "protocol_feature_nonrefundable_transfer_nep491"), - derive(serde::Deserialize) +/// V1 introduces permanent_storage_bytes +#[derive( + BorshSerialize, + BorshDeserialize, + serde::Serialize, + serde::Deserialize, + PartialEq, + Eq, + Debug, + Clone, + ProtocolSchema, )] -#[derive(serde::Serialize, PartialEq, Eq, Debug, Clone, ProtocolSchema)] -pub struct Account { +pub struct AccountV2 { /// The total not locked tokens. - #[serde(with = "dec_format")] amount: Balance, /// The amount locked due to staking. - #[serde(with = "dec_format")] locked: Balance, - /// Permanent storage allowance, additional to what storage staking gives. - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - #[serde(with = "dec_format")] - permanent_storage_bytes: StorageUsage, /// Hash of the code stored in the storage for this account. code_hash: CryptoHash, /// Storage used by the given account, includes account id, this struct, access keys and other data. storage_usage: StorageUsage, - /// Version of Account in re migrations and similar. - #[serde(default)] - version: AccountVersion, + /// Permanent storage allowance, additional to what storage staking gives. + permanent_storage_bytes: StorageUsage, } impl Account { @@ -86,299 +104,245 @@ impl Account { permanent_storage_bytes: StorageUsage, code_hash: CryptoHash, storage_usage: StorageUsage, - #[cfg_attr(not(feature = "protocol_feature_nonrefundable_transfer_nep491"), allow(unused))] protocol_version: ProtocolVersion, ) -> Self { - #[cfg(not(feature = "protocol_feature_nonrefundable_transfer_nep491"))] - let account_version = AccountVersion::V1; - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - let account_version = if checked_feature!("stable", NonrefundableStorage, protocol_version) - { - AccountVersion::V2 + if permanent_storage_bytes > 0 { + assert!(checked_feature!( + "protocol_feature_nonrefundable_transfer_nep491", + NonrefundableStorage, + protocol_version + )); + Self::V2(AccountV2 { + amount, + locked, + permanent_storage_bytes, + code_hash, + storage_usage, + }) } else { - AccountVersion::V1 - }; - if account_version == AccountVersion::V1 { - assert_eq!(permanent_storage_bytes, 0); - } - Account { - amount, - locked, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes, - code_hash, - storage_usage, - version: account_version, + Self::V1(AccountV1 { amount, locked, code_hash, storage_usage }) } } #[inline] pub fn amount(&self) -> Balance { - self.amount - } - - #[inline] - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - pub fn permanent_storage_bytes(&self) -> StorageUsage { - self.permanent_storage_bytes + match self { + Self::V1(account) => account.amount, + Self::V2(account) => account.amount, + } } #[inline] - #[cfg(not(feature = "protocol_feature_nonrefundable_transfer_nep491"))] pub fn permanent_storage_bytes(&self) -> StorageUsage { - 0 + match self { + Self::V1(_) => 0, + Self::V2(account_v1) => account_v1.permanent_storage_bytes, + } } #[inline] pub fn locked(&self) -> Balance { - self.locked + match self { + Self::V1(account) => account.locked, + Self::V2(account) => account.locked, + } } #[inline] pub fn code_hash(&self) -> CryptoHash { - self.code_hash + match self { + Self::V1(account) => account.code_hash, + Self::V2(account) => account.code_hash, + } } #[inline] pub fn storage_usage(&self) -> StorageUsage { - self.storage_usage + match self { + Self::V1(account) => account.storage_usage, + Self::V2(account) => account.storage_usage, + } } #[inline] pub fn version(&self) -> AccountVersion { - self.version + match self { + Self::V1(_) => AccountVersion::V1, + Self::V2(_) => AccountVersion::V2, + } } #[inline] pub fn set_amount(&mut self, amount: Balance) { - self.amount = amount; + match self { + Self::V1(account) => account.amount = amount, + Self::V2(account) => account.amount = amount, + } } #[inline] - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] pub fn set_permanent_storage_bytes(&mut self, permanent_storage_bytes: StorageUsage) { - self.permanent_storage_bytes = permanent_storage_bytes; + match self { + Self::V1(v1) => { + let v1 = AccountV2 { + amount: v1.amount, + locked: v1.locked, + code_hash: v1.code_hash, + storage_usage: v1.storage_usage, + permanent_storage_bytes, + }; + *self = Account::V2(v1); + } + Self::V2(v2) => v2.permanent_storage_bytes = permanent_storage_bytes, + } } #[inline] pub fn set_locked(&mut self, locked: Balance) { - self.locked = locked; + match self { + Self::V1(account) => account.locked = locked, + Self::V2(account) => account.locked = locked, + } } #[inline] pub fn set_code_hash(&mut self, code_hash: CryptoHash) { - self.code_hash = code_hash; + match self { + Self::V1(account) => account.code_hash = code_hash, + Self::V2(account) => account.code_hash = code_hash, + } } #[inline] pub fn set_storage_usage(&mut self, storage_usage: StorageUsage) { - self.storage_usage = storage_usage; - } - - pub fn set_version(&mut self, version: AccountVersion) { - self.version = version; + match self { + Self::V1(account) => account.storage_usage = storage_usage, + Self::V2(account) => account.storage_usage = storage_usage, + } } } -/// These accounts are serialized in merklized state. -/// We keep old accounts in the old format to avoid migration of the MPT. -#[derive( - BorshSerialize, - serde::Serialize, - serde::Deserialize, - PartialEq, - Eq, - Debug, - Clone, - ProtocolSchema, -)] -struct LegacyAccount { - amount: Balance, - locked: Balance, - code_hash: CryptoHash, - storage_usage: StorageUsage, -} - -/// We only allow nonrefundable storage on new accounts (see `LegacyAccount`). -#[derive(BorshSerialize, BorshDeserialize, ProtocolSchema)] -struct AccountV2 { +/// Account representation for serde ser/deser that maintains both backward +/// and forward compatibility. +#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug, Clone, ProtocolSchema)] +struct SerdeAccount { + #[serde(with = "dec_format")] amount: Balance, + #[serde(with = "dec_format")] locked: Balance, + #[serde(with = "dec_format", default, skip_serializing_if = "Option::is_none")] + permanent_storage_bytes: Option, code_hash: CryptoHash, storage_usage: StorageUsage, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes: StorageUsage, + /// Version of Account in re migrations and similar. + #[serde(default)] + version: AccountVersion, } -/// We need custom serde deserialization in order to parse mainnet genesis accounts (LegacyAccounts) -/// as accounts V1. This preserves the mainnet genesis hash. -#[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] impl<'de> serde::Deserialize<'de> for Account { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - #[derive(serde::Deserialize)] - struct AccountData { - #[serde(with = "dec_format")] - amount: Balance, - #[serde(with = "dec_format")] - locked: Balance, - // If the field is missing, serde will use None as the default. - #[serde(default, with = "dec_format")] - permanent_storage_bytes: Option, - code_hash: CryptoHash, - storage_usage: StorageUsage, - #[serde(default)] - version: Option, - } - - let account_data = AccountData::deserialize(deserializer)?; - - match account_data.permanent_storage_bytes { - Some(permanent_storage_bytes) => { - // Given that the `permanent_storage_bytes` field has been serialized, the `version` field must has been serialized too. - let version = match account_data.version { - Some(version) => version, - None => { - return Err(serde::de::Error::custom("missing `version` field")); - } - }; - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - if version < AccountVersion::V2 && permanent_storage_bytes > 0 { + let account_data = SerdeAccount::deserialize(deserializer)?; + match account_data.version { + AccountVersion::V1 => { + if account_data.permanent_storage_bytes.is_some() { return Err(serde::de::Error::custom( - "permanent storage bytes positive amount exists for account version older than V2", + "permanent storage bytes exists for unversioned account", )); } - - Ok(Account { + Ok(Account::V1(AccountV1 { amount: account_data.amount, locked: account_data.locked, code_hash: account_data.code_hash, storage_usage: account_data.storage_usage, + })) + } + AccountVersion::V2 => { + let Some(permanent_storage_bytes) = account_data.permanent_storage_bytes else { + return Err(serde::de::Error::custom( + "permanent storage bytes is missing for V1", + )); + }; + Ok(Account::V2(AccountV2 { + amount: account_data.amount, + locked: account_data.locked, permanent_storage_bytes, - version, - }) + code_hash: account_data.code_hash, + storage_usage: account_data.storage_usage, + })) } - None => Ok(Account { - amount: account_data.amount, - locked: account_data.locked, - code_hash: account_data.code_hash, - storage_usage: account_data.storage_usage, - permanent_storage_bytes: 0, - version: AccountVersion::V1, - }), } } } +impl serde::Serialize for Account { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let version = self.version(); + let permanent_storage_bytes = match version { + AccountVersion::V1 => None, + AccountVersion::V2 => Some(self.permanent_storage_bytes()), + }; + let repr = SerdeAccount { + amount: self.amount(), + locked: self.locked(), + permanent_storage_bytes, + code_hash: self.code_hash(), + storage_usage: self.storage_usage(), + version: version, + }; + repr.serialize(serializer) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +enum BorshVersionedAccount { + // V1 is not included since it is serialized directly without being wrapped in enum + V2(AccountV2), +} + impl BorshDeserialize for Account { fn deserialize_reader(rd: &mut R) -> io::Result { // The first value of all Account serialization formats is a u128, // either a sentinel or a balance. let sentinel_or_amount = u128::deserialize_reader(rd)?; if sentinel_or_amount == Account::SERIALIZATION_SENTINEL { - if cfg!(not(feature = "protocol_feature_nonrefundable_transfer_nep491")) { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("account serialization sentinel not allowed for AccountV1"), - )); - } - - // Account v2 or newer. - let version_byte = u8::deserialize_reader(rd)?; - let version = AccountVersion::try_from(version_byte).map_err(|_| { - io::Error::new( - io::ErrorKind::InvalidData, - format!( - "error deserializing account: invalid account version {}", - version_byte - ), - ) - })?; - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - if version < AccountVersion::V2 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("expected account version 2 or higher, got {:?}", version), - )); - } - let account = AccountV2::deserialize_reader(rd)?; - - Ok(Account { - amount: account.amount, - locked: account.locked, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes: account.permanent_storage_bytes, - code_hash: account.code_hash, - storage_usage: account.storage_usage, - version, - }) + let versioned_account = BorshVersionedAccount::deserialize_reader(rd)?; + let account = match versioned_account { + BorshVersionedAccount::V2(account_v1) => Account::V2(account_v1), + }; + Ok(account) } else { - // Account v1 + // Legacy unversioned representation of Account let locked = u128::deserialize_reader(rd)?; let code_hash = CryptoHash::deserialize_reader(rd)?; let storage_usage = StorageUsage::deserialize_reader(rd)?; - Ok(Account { + Ok(Account::V1(AccountV1 { amount: sentinel_or_amount, locked, code_hash, storage_usage, - version: AccountVersion::V1, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes: 0, - }) + })) } } } impl BorshSerialize for Account { fn serialize(&self, writer: &mut W) -> io::Result<()> { - let legacy_account = LegacyAccount { - amount: self.amount(), - locked: self.locked(), - code_hash: self.code_hash(), - storage_usage: self.storage_usage(), - }; - - #[cfg(not(feature = "protocol_feature_nonrefundable_transfer_nep491"))] - { - legacy_account.serialize(writer) - } - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - { - match self.version { - // It might be tempting to lazily convert old V1 to V2 - // while serializing. But that would break the borsh assumptions - // of unique binary representation. - AccountVersion::V1 => { - if self.permanent_storage_bytes > 0 { - panic!("Trying to serialize V1 account with permanent_storage_bytes"); - } - legacy_account.serialize(writer) - } - AccountVersion::V2 => { - let account = AccountV2 { - amount: self.amount(), - locked: self.locked(), - code_hash: self.code_hash(), - storage_usage: self.storage_usage(), - permanent_storage_bytes: self.permanent_storage_bytes(), - }; - let sentinel = Account::SERIALIZATION_SENTINEL; - // For now a constant, but if we need V3 later we can use this - // field instead of sentinel magic. - let version = 2u8; - BorshSerialize::serialize(&sentinel, writer)?; - BorshSerialize::serialize(&version, writer)?; - account.serialize(writer) - } + let versioned_account = match self { + Account::V1(unversioned_account) => { + return unversioned_account.serialize(writer) } - } + Account::V2(account_v1) => BorshVersionedAccount::V2(account_v1.clone()), + }; + let sentinel = Account::SERIALIZATION_SENTINEL; + BorshSerialize::serialize(&sentinel, writer)?; + BorshSerialize::serialize(&versioned_account, writer) } } @@ -478,8 +442,6 @@ pub struct FunctionCallPermission { #[cfg(test)] mod tests { - - use crate::hash::hash; #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] use crate::version::ProtocolFeature; @@ -498,22 +460,28 @@ mod tests { } #[test] - fn test_legacy_account_serde_serialization() { - let old_account = LegacyAccount { + fn test_v1_account_serde_serialization() { + let old_account = AccountV1 { amount: 1_000_000, locked: 1_000_000, - code_hash: CryptoHash::default(), + code_hash: CryptoHash::hash_bytes(&[42]), storage_usage: 100, }; let serialized_account = serde_json::to_string(&old_account).unwrap(); + let expected_serde_repr = SerdeAccount { + amount: old_account.amount, + locked: old_account.locked, + permanent_storage_bytes: None, + code_hash: old_account.code_hash, + storage_usage: old_account.storage_usage, + version: AccountVersion::V1, + }; + let actual_serde_repr: SerdeAccount = serde_json::from_str(&serialized_account).unwrap(); + assert_eq!(actual_serde_repr, expected_serde_repr); + let new_account: Account = serde_json::from_str(&serialized_account).unwrap(); - assert_eq!(new_account.amount(), old_account.amount); - assert_eq!(new_account.locked(), old_account.locked); - assert_eq!(new_account.code_hash(), old_account.code_hash); - assert_eq!(new_account.storage_usage(), old_account.storage_usage); - assert_eq!(new_account.permanent_storage_bytes(), 0); - assert_eq!(new_account.version, AccountVersion::V1); + assert_eq!(new_account, Account::V1(old_account)); let new_serialized_account = serde_json::to_string(&new_account).unwrap(); let deserialized_account: Account = serde_json::from_str(&new_serialized_account).unwrap(); @@ -521,56 +489,62 @@ mod tests { } #[test] - fn test_legacy_account_borsh_serialization() { - let old_account = LegacyAccount { + fn test_v1_account_borsh_serialization() { + let old_account = AccountV1 { amount: 100, locked: 200, - code_hash: CryptoHash::default(), + code_hash: CryptoHash::hash_bytes(&[42]), storage_usage: 300, }; - let mut old_bytes = &borsh::to_vec(&old_account).unwrap()[..]; - let new_account = ::deserialize(&mut old_bytes).unwrap(); + let old_bytes = borsh::to_vec(&old_account).unwrap(); + let new_account = ::deserialize(&mut &old_bytes[..]).unwrap(); + assert_eq!(new_account, Account::V1(old_account)); - assert_eq!(new_account.amount(), old_account.amount); - assert_eq!(new_account.locked(), old_account.locked); - assert_eq!(new_account.code_hash(), old_account.code_hash); - assert_eq!(new_account.storage_usage(), old_account.storage_usage); - assert_eq!(new_account.version, AccountVersion::V1); - - let mut new_bytes = &borsh::to_vec(&new_account).unwrap()[..]; + let new_bytes = borsh::to_vec(&new_account).unwrap(); + assert_eq!(new_bytes, old_bytes); let deserialized_account = - ::deserialize(&mut new_bytes).unwrap(); + ::deserialize(&mut &new_bytes[..]).unwrap(); assert_eq!(deserialized_account, new_account); } #[test] - fn test_account_v1_serde_serialization() { - let account = Account { + fn test_account_v2_serde_serialization() { + let account_v2 = AccountV2 { amount: 10_000_000, locked: 100_000, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes: 0, - code_hash: CryptoHash::default(), + permanent_storage_bytes: 2000, + code_hash: CryptoHash::hash_bytes(&[42]), storage_usage: 1000, - version: AccountVersion::V1, }; + let account = Account::V2(account_v2.clone()); + let serialized_account = serde_json::to_string(&account).unwrap(); + let expected_serde_repr = SerdeAccount { + amount: account_v2.amount, + locked: account_v2.locked, + permanent_storage_bytes: Some(account_v2.permanent_storage_bytes), + code_hash: account_v2.code_hash, + storage_usage: account_v2.storage_usage, + version: AccountVersion::V2, + }; + let actual_serde_repr: SerdeAccount = serde_json::from_str(&serialized_account).unwrap(); + assert_eq!(actual_serde_repr, expected_serde_repr); + let deserialized_account: Account = serde_json::from_str(&serialized_account).unwrap(); assert_eq!(deserialized_account, account); } - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - /// It is impossible to construct V1 account with permanent_storage_bytes greater than 0. + /// It is impossible to construct unversioned account with permanent_storage_bytes greater than 0. /// So the situation in this test is theoretical. /// - /// Serialization of account V1 with permanent_storage_bytes amount greater than 0 would pass without an error, + /// Serialization of unversioned account with permanent_storage_bytes amount greater than 0 would pass without an error, /// but an error would be raised on deserialization of such invalid data. #[test] - fn test_account_v1_serde_serialization_invalid_data() { - let account = Account { + fn test_account_v2_serde_serialization_invalid_data() { + let account = SerdeAccount { amount: 10_000_000, locked: 100_000, - permanent_storage_bytes: 1, + permanent_storage_bytes: Some(1), code_hash: CryptoHash::default(), storage_usage: 1000, version: AccountVersion::V1, @@ -582,94 +556,16 @@ mod tests { } #[test] - fn test_account_v1_borsh_serialization() { - let account = Account { - amount: 1_000_000, - locked: 1_000_000, - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - permanent_storage_bytes: 0, - code_hash: CryptoHash::default(), - storage_usage: 100, - version: AccountVersion::V1, - }; - let serialized_account = borsh::to_vec(&account).unwrap(); - assert_eq!( - &hash(&serialized_account).to_string(), - "EVk5UaxBe8LQ8r8iD5EAxVBs6TJcMDKqyH7PBuho6bBJ" - ); - let deserialized_account = - ::deserialize(&mut &serialized_account[..]).unwrap(); - assert_eq!(deserialized_account, account); - } - - #[cfg(not(feature = "protocol_feature_nonrefundable_transfer_nep491"))] - #[test] - #[should_panic(expected = "account serialization sentinel not allowed for AccountV1")] - fn test_account_v1_borsh_serialization_sentinel() { - let account = Account { - amount: Account::SERIALIZATION_SENTINEL, - locked: 1_000_000, - code_hash: CryptoHash::default(), - storage_usage: 100, - version: AccountVersion::V1, - }; - let serialized_account = borsh::to_vec(&account).unwrap(); - ::deserialize(&mut &serialized_account[..]).unwrap(); - } - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - /// It is impossible to construct V1 account with permanent_storage_bytes greater than 0. - /// So the situation in this test is theoretical. - /// - /// If a V1 account had permanent_storage_bytes greater than zero, it would panic during Borsh serialization. - #[test] - #[should_panic(expected = "Trying to serialize V1 account with permanent_storage_bytes")] - fn test_account_v1_borsh_serialization_invalid_data() { - let account = Account { - amount: 1_000_000, - locked: 1_000_000, - permanent_storage_bytes: 1, - code_hash: CryptoHash::default(), - storage_usage: 100, - version: AccountVersion::V1, - }; - let _ = borsh::to_vec(&account); - } - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - #[test] - fn test_account_v2_serde_serialization() { - let account = Account { + fn test_account_v2_borsh_serialization() { + let account_v2 = AccountV2 { amount: 10_000_000, locked: 100_000, - permanent_storage_bytes: 37, - code_hash: CryptoHash::default(), + permanent_storage_bytes: 2000, + code_hash: CryptoHash::hash_bytes(&[42]), storage_usage: 1000, - version: AccountVersion::V2, - }; - let serialized_account = serde_json::to_string(&account).unwrap(); - let deserialized_account: Account = serde_json::from_str(&serialized_account).unwrap(); - assert_eq!(deserialized_account, account); - } - - #[cfg(feature = "protocol_feature_nonrefundable_transfer_nep491")] - #[test] - fn test_account_v2_borsh_serialization() { - let account = Account { - amount: 1_000_000, - locked: 1_000_000, - permanent_storage_bytes: 42, - code_hash: CryptoHash::default(), - storage_usage: 100, - version: AccountVersion::V2, }; + let account = Account::V2(account_v2.clone()); let serialized_account = borsh::to_vec(&account).unwrap(); - if cfg!(feature = "protocol_feature_nonrefundable_transfer_nep491") { - expect_test::expect!("G2Dn8ABMPqsoXuQCPR9yV19HgBi3ZJNqEMEntyLqveDR") - } else { - expect_test::expect!("EVk5UaxBe8LQ8r8iD5EAxVBs6TJcMDKqyH7PBuho6bBJ") - } - .assert_eq(&hash(&serialized_account).to_string()); let deserialized_account = ::deserialize(&mut &serialized_account[..]).unwrap(); assert_eq!(deserialized_account, account);