Skip to content

feat: x/precisebank module #69

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

Open
wants to merge 73 commits into
base: main
Choose a base branch
from
Open

feat: x/precisebank module #69

wants to merge 73 commits into from

Conversation

dudong2
Copy link

@dudong2 dudong2 commented Apr 13, 2025

Description

TL;DR: Introduces x/precisebank to address EVM ↔ SDK precision mismatch and integrates it with x/vm and precompiles.


Problem

  • Cosmos SDK assumes a 6-decimal denomination across all modules.
  • EVM uses 18 decimals, causing precision loss when transferring values or paying gas.
  • Sub-6-decimal values are floored, which can lead to:
    • Loss of small token amounts
    • Arbitrage vulnerabilities in DEXs like Uniswap v2 navite coin -> erc20 senarios due to mismatched balance handling

Purpose

  • Enable precise handling of fractional balances in EVM-related modules.
  • Preserve and manage sub-integer values (e.g., 0.0000001 (= 10^-7) tokens) without loss.
  • Seamlessly integrate precise value tracking into x/vm and precompiles.

Implemented Changes

  • Introduced a new x/precisebank module based on Kava’s implementation.
  • Generalized to support 1~18 decimal assets, not just 6↔18.
  • Added carry/borrow mechanics for converting fractional to integer coins.
  • Replaced direct use of BankKeeper with PreciseBankKeeper in:
    • x/vm
    • precompiles
  • Added invariant check for total supply
  • Added CLI queries for fractional balances and remainder state.
  • Added tests:
    • Edge case handling
    • Fuzz tests (Mint, Burn, Send, Genesis validation)
    • Random value tests
    • Contract tests(simple wrap/unwarp)

Notes

  • For tokens with exactly 18 decimals, using x/precisebank is supported but not necessary — the standard x/bank module is sufficient and recommended.
  • The x/precisebank module is designed to manage only the fractional (sub-integer) balances, while integer balances continue to be managed by the x/bank module..
  • Staking, slashing, distribution, and other Cosmos SDK modules remain unchanged:
    • They all assume 6 decimals and are out of scope for this PR.

Closes: #48


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • tackled an existing issue or discussed with a team member
  • left instructions on how to review the changes
  • targeted the main branch

Reviewers Checklist

All items are required.
Please add a note if the item is not applicable
and please add your handle next to the items reviewed
if you only reviewed selected items.

I have...

  • added a relevant changelog entry to the Unreleased section in CHANGELOG.md
  • confirmed all author checklist items have been addressed
  • confirmed that this PR does not change production code
  • reviewed content
  • tested instructions (if applicable)
  • confirmed all CI checks have passed

@dudong2 dudong2 self-assigned this Apr 13, 2025
@dudong2 dudong2 marked this pull request as ready for review April 27, 2025 15:30
@dudong2 dudong2 requested review from vladjdk, dongsam and zsystm April 28, 2025 02:02
Copy link
Member

@dongsam dongsam left a comment

Choose a reason for hiding this comment

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

Nice work 👍

I haven’t completed a full review yet, but I’m leaving a note on the key comments so far, as other reviewers may also come across them.
I’ll continue with a second round of review and provide further comments afterward.


About License/Copyright
It may be necessary to review whether a notice is required to comply with the Apache License or Copyright for the file copied from Kava’s preciseBank module

JSON-RPC Integration Test
Although the code for the GetBalance endpoint in JSON-RPC hasn’t changed, the internal logic now uses precisebank as wrapped bank.
Adding an integration test or including local test results could be helpful in verifying this behavior.
While the internal logic appears to be well covered in view_test.go, it would be beneficial to validate this change specifically in the context of JSON-RPC to catch any unexpected issues during integration.

Comment on lines 27 to 32
if denom == types.ExtendedCoinDenom && addr.Equals(k.ak.GetModuleAddress(types.ModuleName)) {
return sdk.NewCoin(denom, sdkmath.ZeroInt())
}

// Pass through to x/bank for denoms except ExtendedCoinDenom
if denom != types.ExtendedCoinDenom {
Copy link
Member

Choose a reason for hiding this comment

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

types.ExtendedCoinDenom is hardcoded as aatom, which seems dangerous to use.
I suspect the original intention might have been to use types.GetEVMCoinDenom() instead?

If another app chain uses precisebnak module for EVM bank without properly configuring these unclear settings, it could lead to unintended behavior or errors.

// Decimals is the number of decimal places for the fractional balance from EVM.
Decimals = evmtypes.SixDecimals
// IntegerCoinDenom is the denomination for integer coins that are managed by
// x/bank. This is the "true" denomination of the coin, and is also used for
// the reserve to back all fractional coins.
IntegerCoinDenom = "uatom"
// ExtendedCoinDenom is the denomination for the extended IntegerCoinDenom. This
// not only represents the fractional balance, but the total balance of
// integer + fractional balances.
ExtendedCoinDenom = "aatom"

// GetEVMCoinDenom returns the denom used for the EVM coin.
func GetEVMCoinDenom() string {
return evmCoinInfo.Denom
}

evm/evmd/app.go

Lines 140 to 142 in 144cd48

precisebanktypes.Decimals = evmtypes.SixDecimals
precisebanktypes.IntegerCoinDenom = "uatom"
precisebanktypes.ExtendedCoinDenom = "aatom"

Currently, both assigning values to precisebanktypes.* in app.go’s init function and using evmCoinInfo coexist.
To prevent mistakes, it seems better to unify these into a single approach.

If this issue is confirmed, the above code is just one example, and we need to conduct a comprehensive review, refactor, and unify usage across the entire codebase, not just in this location.

Alternatively, if this was done due to dependencies between modules,
how about defining it at the type level in the root directory instead?

Copy link
Author

Choose a reason for hiding this comment

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

I removed that variables in ee976de.
But I'm not sure if this is the right way to do it, because then I would be setting evm coin info using the value in client.toml instead of the --chain-id option via the cli. I would like to hear your opinion.

Copy link
Member

Choose a reason for hiding this comment

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

I confirmed that the update has been applied and coin info is now unified and managed through a single variable 👍

But I’m not fully clear on why this change would affect client.toml and --chain-id. From what I can see, initClientCtx internally calls WithChainID(flags.FlagChainID) through ReadPersistentCommandFlags, so is there an issue I’m missing or something fundamentally unsolvable here?

If there’s any concern, I think it’s a good idea to leave this comment open for now and get input from other reviewers as well.

Copy link
Author

Choose a reason for hiding this comment

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

The current change is reading the client.toml via ReadFromClientConfig, setting the chain id and calling EvmAppOptions. And if you look at the internal implementation of EvmAppOptions, once it is set via the seal variable, it cannot be set again. This means that after making this change, the option --chain-id will not be available when setting up the evm configurator.

Copy link
Member

@vladjdk vladjdk left a comment

Choose a reason for hiding this comment

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

Thanks for the work!

I left a few comments - few nits, and some questions.

My main concern is that we seem to no longer be converting the gas to the base chain's denom. e.g. If a chain is 6 decimals, it looks like we now require the gas to be 18 decimals. The txBuilder currently converts the decimals, but we changed the function to convert the denom to atto units. This means that we expect transactions to submit an atto unit tx fee. In this case, we'd also need to submit gas for other Cosmos transactions in the atto fee. For chains that currently run 6 decimals, the implications here are pretty big. They'd need to change their base fees on their nodes, inside wallets, and the way dapps generate/estimate fees.

My recommendation would be to keep the conversion for gas as it was. Since we were originally implementing precisebank to solve for data loss in partial gas token sending on the EVM itself, it doesn't seem like gas would cause an issue. I don't agree with the implied assumption that chains will be willing to change their base gas token for everything that depends on it. I think we should instead open another issue where we support multiple gas tokens. Then, we can support atto token gas on the EVM side, but keep micro token gas support on the SDK side.

Please correct me if I'm understanding this wrong, and let me know your thoughts.

@dudong2 dudong2 requested review from vladjdk and dongsam May 2, 2025 06:53
Copy link
Member

@vladjdk vladjdk left a comment

Choose a reason for hiding this comment

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

Okay, I did some testing, and it looks like atto gas and micro gas are converting properly on both ends. Tested both Ethereum transactions and Cosmos transactions with the Feemarket and base denom set up to be 0.0001utest. Confirming that after a manual check, price/gas

However, in a future issue, we really need to set up E2E testing to test gas from an external perspective. It was pretty hard to follow along with the code to see if both tokens are converting to the same gas fee amount in the end.

Nothing specific to your change, though. We're fine to make testing improvements when we start our testing phase.

Thanks for the work :)

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.

Decimal handling (6 and 18)
3 participants