-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
|
||
| return None | ||
| }; | ||
| let account = T::decode(&mut TrailingZeroInput(b)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO the check above is wrong, because if you for example have an enum If you want to make it correct, you need to extend
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you know that the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes therefore we could add a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 using Maybe one trick here could be to decode, then re-encode, and see if we go back to the same bytes. A bit hacky and also assumes reversible encode/decode.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did something like what @kianenigma suggested. I check the length of the bytes used for the seed, and check that the length of bytes of the account is greater or equal to, otherwise, we know some bytes were truncated.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. I also tried my luck on this but could not create a test that the function actually returns
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can just create a mock environment where account id is 32 bytes? |
||
| .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.