Skip to content
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

[Flow EVM] balance type improvement #5271

Merged
merged 11 commits into from
Jan 26, 2024
Merged

Conversation

ramtinms
Copy link
Contributor

@ramtinms ramtinms commented Jan 23, 2024

This PR updates the balance type definition to use a *big.Int under the hood instead of the Ufix64 which has less precision.
The balance object is used for multiple purposes, such as returning the balance for an account or setting an amount to be bridged.
Given that we will let accounts balances show the AttoFlow amounts, we need to make sure the underlying data structure can have the precision needed. We still need to protect users when calling withdraw with a balance that could result in rounding off errors.
To do so we are exposing new utility functions on the Balance method and we are adding this protection to the withdrawal request.

We are protecting users from FLOW loss during operations though maybe for the Cadence side, we protect the value from access and let users call a method called castToUFix64 or something similar to know about the rounding error that could happen.

@codecov-commenter
Copy link

codecov-commenter commented Jan 23, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (5bf6cb3) 55.60% compared to head (1bbc048) 53.44%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5271      +/-   ##
==========================================
- Coverage   55.60%   53.44%   -2.17%     
==========================================
  Files        1002      224     -778     
  Lines       96627    22049   -74578     
==========================================
- Hits        53725    11783   -41942     
+ Misses      38845     9286   -29559     
+ Partials     4057      980    -3077     
Flag Coverage Δ
unittests 53.44% <ø> (-2.17%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ramtinms ramtinms marked this pull request as ready for review January 23, 2024 19:05
fvm/evm/types/balance.go Outdated Show resolved Hide resolved
func (b Balance) ToAttoFlow() *big.Int {
return new(big.Int).Mul(new(big.Int).SetUint64(uint64(b)), SmallestAcceptableBalanceValueInAttoFlow)
// HasUFix64RoundingError returns true if casting to UFix64 has rounding error
func (b *Balance) HasUFix64RoundingError() bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to avoid having this method as an explicit check, instead encapsulating it so there's no state that this could be possible? What I'm trying to say is, that a developer might forget to run this check and thus it's bug-prone. If I understand correctly the only place a balance could be created that would lead to rounding error is by using NewBalanceFromAttoFlow so why wouldn't the check be part of that factory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typical developer doesn't have to deal with structure, they use the Cadence one, this is on the backbone.
initially we had the approach you suggested but, when you query an account balance and account state has been created then you have some balance that can be rounded off when converting to UFix but user might ask for AttoFlow value and we can't ignore the real balance of the account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Makes sense.

if b.Value.Cmp(otherInAtto) == -1 {
return ErrWithdrawBalanceRounding
}
b.Value = new(big.Int).Sub(b.Value, otherInAtto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this result could be a value that produces a rounding error when converted to Flow no?

// no need to check for overflow, as go does it
return Balance(uint64(b) + uint64(other))
b.Value = new(big.Int).Add(b.Value, other.ToAttoFlow())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could also lead to a rounding error after converting to flow ufix no?

ErrBalanceConversion = errors.New("balance converion error")
// ErrWithdrawBalanceRounding is returned when withdraw call has a balance that could
// yeild to rounding error, i.e. the balance contains fractions smaller than 10^8 Flow (smallest unit allowed to transfer).
ErrWithdrawBalanceRounding = errors.New("withdraw failed! balance is susceptible to the rounding error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add what is the biggest precision we support in the error description so the user knows how to react

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, not sure if I follow, we have 10^8 in the go doc?

@ramtinms
Copy link
Contributor Author

ramtinms commented Jan 24, 2024

Let me clarify what the purpose of this PR is:

  • we update to maintain the highest precision as part of the internal balance structure to reflect the real balance of accounts for two reasons:

    • its going to be available to users to know the full balance in ToAttoFlow
    • even the original balance doesn't need the full precision, operations could result in small fractions be on the balance
  • we consider the rounding error when a balance is casted to UFix64 as an operational side effect, so for some operations we let the user control it, yet for some operations like Withdraw we protect users.
    example of this could be, a user ask for the balance of the account and then call withdraw, then if the balance has small fractions, then it might result in wrong expectation of the operation.

@ramtinms
Copy link
Contributor Author

one option that I initially started what when you do the UFix casting we also return a boolean giving heads up about the potential rounding error.

@sideninja
Copy link
Contributor

  • example

This makes sense.

@sideninja sideninja self-requested a review January 25, 2024 12:45
@ramtinms ramtinms requested a review from turbolent January 25, 2024 18:55
@ramtinms ramtinms enabled auto-merge January 26, 2024 04:53
@ramtinms ramtinms added this pull request to the merge queue Jan 26, 2024
Merged via the queue into master with commit 9857ec6 Jan 26, 2024
50 of 51 checks passed
@ramtinms ramtinms deleted the ramtin/evm-improve-balance-type branch January 26, 2024 05:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants