-
Notifications
You must be signed in to change notification settings - Fork 47
refactor: use U256 for infallible scaling #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| let decimals = U256::from(10) | ||
| .checked_pow(denomination.into()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
denomination is now cast to a U256, which seems a little dumb. We could also stick to using a u128 to perform this calculation (raising to the power of denomination) and then convert the result to U256. Not sure what's more efficient though.
| let mut x_u256 = U256::from(x); | ||
| // Shift left to produce the representation that our fixed type would have (but | ||
| // with extra integer bits that would potentially not fit in the fixed type). | ||
| x_u256.shl_assign(CurveParameterTypeOf::<T>::frac_nbits()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea if this can panic? I didn't find a checked_ variant of shl, so probably not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macro is implementing the trait:
impl<T> $crate::core_::ops::ShlAssign<T> for $name where T: Into<$name> {
fn shl_assign(&mut self, shift: T) {
*self = *self << shift;
}
}
the << operation can panic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am against removing the checks from the runtime. Anything that the type system does not guarantee against should be checked, and having it in the runtime does not imply removing the static tests for the config itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok because there is no checked implementation, I would then implement a check to be run at each invocation of the function determining if the types have been chosen correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually as long as we don't have this configurable this cannot panic, because the U256 is large enough to work with any Fixed type that exists
| pub(crate) fn convert_to_fixed<T: Config>(x: u128, denomination: u8) -> Result<CurveParameterTypeOf<T>, ArithmeticError> | ||
| where | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: TryFrom<u128>, | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: TryFrom<U256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could in principle make this more generic by not fixing the type of x (an Into<U256> trait bound or similar is enough) and by making the type of the big integer configurable in the runtime, so you can match it to the chosen balance and fixed types.
The big integer only needs to be the size of the balance type plus the size of the fractional bits of the fixed, so for a u64 balance and a I75F53 fixed a u128 would be sufficient for conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a TODO in cffd4bd
| where | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: Copy + ToFixed + AddAssign + BitOrAssign + ShlAssign + TryFrom<u128>, | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: Copy + ToFixed + AddAssign + BitOrAssign + ShlAssign + TryFrom<U256>, | ||
| CollateralCurrenciesBalanceOf<T>: Into<U256> + TryFrom<U256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using bigger integers is also a better fix for the overflow issues we had when computing the refund amounts. This second trait bound is for this fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also make this large integer type configurable, it just needs to have twice the size of the balance type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong opinion on this. I don’t think any other project is using a balance type larger than u128.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thing is you can also configure it to be smaller, avoiding blowing it up to a U256 when it's completely unnecessary, as it would be if your balance is just a u64 (where a u128 would be enough)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a TODO in cffd4bd
| .fold(U256::from(0), |sum, id| { | ||
| sum.saturating_add(T::Fungibles::total_issuance(id).into()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can now also no longer overflow
| defensive_assert!( | ||
| sum_of_issuances >= burnt, | ||
| "burnt amount exceeds the total supply of all bonded currencies" | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can panic or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does not panic in production - it does though in non-optimised builds
| where | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: Copy + ToFixed + AddAssign + BitOrAssign + ShlAssign + TryFrom<u128>, | ||
| <CurveParameterTypeOf<T> as Fixed>::Bits: Copy + ToFixed + AddAssign + BitOrAssign + ShlAssign + TryFrom<U256>, | ||
| CollateralCurrenciesBalanceOf<T>: Into<U256> + TryFrom<U256>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong opinion on this. I don’t think any other project is using a balance type larger than u128.
| let mut x_u256 = U256::from(x); | ||
| // Shift left to produce the representation that our fixed type would have (but | ||
| // with extra integer bits that would potentially not fit in the fixed type). | ||
| x_u256.shl_assign(CurveParameterTypeOf::<T>::frac_nbits()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macro is implementing the trait:
impl<T> $crate::core_::ops::ShlAssign<T> for $name where T: Into<$name> {
fn shl_assign(&mut self, shift: T) {
*self = *self << shift;
}
}
the << operation can panic
Little bug in the total sum calculation
I realised that scaling may be done more elegantly and less likely to overflow by using a U256 to implement what essentially amounts to direct division on a fixed point number with >= 128 integer bits. This way the only overflow we can get is when the result (the scaled value) is too large to fit into the target type.
In other words, the scaling logic is infallible, with the only error case being that the scaling factor is too small, so that the result cannot be represented.
I wanted to ask for your opinion on this solution before I apply it to my pending PR.
Checklist:
array[3]useget(3), ...)