-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Explicitly note that existing AccountIdConversion is truncating and add fallible try_into...
#10719
Changes from 6 commits
da8adfb
fb51483
d84fcbc
d5f4708
bcba8e3
e8b3f01
8a48514
7c21284
a7b57c1
8342927
4c714f8
ab6e169
0caa13e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1252,38 +1252,71 @@ impl<'a> codec::Input for TrailingZeroInput<'a> { | |
|
|
||
| /// This type can be converted into and possibly from an AccountId (which itself is generic). | ||
| pub trait AccountIdConversion<AccountId>: Sized { | ||
| /// Convert into an account ID. This is infallible. | ||
| fn into_account(&self) -> AccountId { | ||
| self.into_sub_account(&()) | ||
| /// Convert into an account ID. This is infallible, and may truncate bytes to provide a result. | ||
| /// This may lead to duplicate accounts if the size of `AccountId` is less than the seed. | ||
| fn into_account_truncating(&self) -> AccountId { | ||
| self.into_sub_account_truncating(&()) | ||
| } | ||
|
|
||
| /// Convert into an account ID, checking that all bytes of the seed are being used in the final | ||
| /// `AccountId` generated. If any bytes are dropped, this returns `None`. | ||
| fn try_into_account(&self) -> Option<AccountId> { | ||
| self.try_into_sub_account(&()) | ||
| } | ||
|
|
||
| /// Try to convert an account ID into this type. Might not succeed. | ||
| fn try_from_account(a: &AccountId) -> Option<Self> { | ||
| Self::try_from_sub_account::<()>(a).map(|x| x.0) | ||
| } | ||
|
|
||
| /// Convert this value amalgamated with the a secondary "sub" value into an account ID. This is | ||
| /// infallible. | ||
| /// Convert this value amalgamated with the a secondary "sub" value into an account ID, | ||
| /// truncating any unused bytes. This is infallible. | ||
| /// | ||
| /// NOTE: The account IDs from this and from `into_account` are *not* guaranteed to be distinct | ||
| /// for any given value of `self`, nor are different invocations to this with different types | ||
| /// `T`. For example, the following will all encode to the same account ID value: | ||
| /// - `self.into_sub_account(0u32)` | ||
| /// - `self.into_sub_account(vec![0u8; 0])` | ||
| /// - `self.into_account()` | ||
| fn into_sub_account<S: Encode>(&self, sub: S) -> AccountId; | ||
| /// | ||
| /// Also, if the seed provided to this function is greater than the number of bytes which fit | ||
| /// into this `AccountId` type, then it will lead to truncation of the seed, and potentially | ||
| /// non-unique accounts. | ||
| fn into_sub_account_truncating<S: Encode>(&self, sub: S) -> AccountId; | ||
|
|
||
| /// Same as `into_sub_account`, but ensuring that all bytes of the account's seed are used when | ||
| /// generating an account. This can help guarantee that different accounts are unique, besides | ||
| /// types which encode the same as noted above. | ||
| fn try_into_sub_account<S: Encode>(&self, sub: S) -> Option<AccountId>; | ||
|
|
||
| /// Try to convert an account ID into this type. Might not succeed. | ||
| fn try_from_sub_account<S: Decode>(x: &AccountId) -> Option<(Self, S)>; | ||
| } | ||
|
|
||
| /// Format is TYPE_ID ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing zeroes to | ||
| /// Format is TYPE_ID ++ encode(sub-seed) ++ 00.... where 00... is indefinite trailing zeroes to | ||
| /// fill AccountId. | ||
| impl<T: Encode + Decode, Id: Encode + Decode + TypeId> AccountIdConversion<T> for Id { | ||
| fn into_sub_account<S: Encode>(&self, sub: S) -> T { | ||
| impl<T: Encode + Decode + MaxEncodedLen, Id: Encode + Decode + TypeId> AccountIdConversion<T> | ||
| for Id | ||
| { | ||
| // Take the `sub` seed, and put as much of it as possible into the generated account, but | ||
| // allowing truncation of the seed if it would not fit into the account id in full. This can | ||
| // lead to two different `sub` seeds with the same account generated. | ||
| fn into_sub_account_truncating<S: Encode>(&self, sub: S) -> T { | ||
| (Id::TYPE_ID, self, sub) | ||
| .using_encoded(|b| T::decode(&mut TrailingZeroInput(b))) | ||
| .expect("`AccountId` type is never greater than 32 bytes; qed") | ||
| .expect("All byte sequences are valid `AccountIds`; qed") | ||
| } | ||
|
|
||
| // Same as `into_sub_account_truncating`, but returns `None` if any bytes would be truncated. | ||
| fn try_into_sub_account<S: Encode>(&self, sub: S) -> Option<T> { | ||
| (Id::TYPE_ID, self, sub).using_encoded(|b| { | ||
| if b.len() > T::max_encoded_len() { | ||
shawntabrizi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return None | ||
| }; | ||
| let account = T::decode(&mut TrailingZeroInput(b)) | ||
|
||
| .expect("All byte sequences are valid `AccountIds`; qed"); | ||
| return Some(account) | ||
| }) | ||
| } | ||
|
|
||
| fn try_from_sub_account<S: Decode>(x: &T) -> Option<(Self, S)> { | ||
|
|
@@ -1655,6 +1688,13 @@ mod tests { | |
| let _ = s.verify(&[0u8; 100][..], &Public::unchecked_from([0; 32])); | ||
| } | ||
|
|
||
| #[derive(Encode, Decode, Default, PartialEq, Debug)] | ||
| struct U128Value(u128); | ||
| impl super::TypeId for U128Value { | ||
| const TYPE_ID: [u8; 4] = [0x0d, 0xf0, 0x0d, 0xf0]; | ||
| } | ||
| // f00df00d | ||
|
|
||
| #[derive(Encode, Decode, Default, PartialEq, Debug)] | ||
| struct U32Value(u32); | ||
| impl super::TypeId for U32Value { | ||
|
|
@@ -1672,23 +1712,46 @@ mod tests { | |
| type AccountId = u64; | ||
|
|
||
| #[test] | ||
| fn into_account_should_work() { | ||
| let r: AccountId = U32Value::into_account(&U32Value(0xdeadbeef)); | ||
| fn into_account_truncating_should_work() { | ||
| let r: AccountId = U32Value::into_account_truncating(&U32Value(0xdeadbeef)); | ||
| assert_eq!(r, 0x_deadbeef_cafef00d); | ||
| } | ||
|
|
||
| #[test] | ||
| fn try_into_account_should_work() { | ||
| let r: AccountId = U32Value::try_into_account(&U32Value(0xdeadbeef)).unwrap(); | ||
| assert_eq!(r, 0x_deadbeef_cafef00d); | ||
|
|
||
| // u128 is bigger than u64 would fit | ||
| let maybe: Option<AccountId> = U128Value::try_into_account(&U128Value(u128::MAX)); | ||
| assert!(maybe.is_none()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn try_from_account_should_work() { | ||
| let r = U32Value::try_from_account(&0x_deadbeef_cafef00d_u64); | ||
| assert_eq!(r.unwrap(), U32Value(0xdeadbeef)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn into_account_with_fill_should_work() { | ||
| let r: AccountId = U16Value::into_account(&U16Value(0xc0da)); | ||
| fn into_account_truncating_with_fill_should_work() { | ||
| let r: AccountId = U16Value::into_account_truncating(&U16Value(0xc0da)); | ||
| assert_eq!(r, 0x_0000_c0da_f00dcafe); | ||
| } | ||
|
|
||
| #[test] | ||
| fn try_into_sub_account_should_work() { | ||
| let r: AccountId = U16Value::try_into_account(&U16Value(0xc0da)).unwrap(); | ||
| assert_eq!(r, 0x_0000_c0da_f00dcafe); | ||
|
|
||
| let maybe: Option<AccountId> = U16Value::try_into_sub_account( | ||
| &U16Value(0xc0da), | ||
| "a really large amount of additional encoded information which will certainly overflow the account id type ;)" | ||
| ); | ||
|
|
||
| assert!(maybe.is_none()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn try_from_account_with_fill_should_work() { | ||
| let r = U16Value::try_from_account(&0x0000_c0da_f00dcafe_u64); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.