PreciseBank

Extended precision wrapper for x/bank enabling 18 decimal support

The x/precisebank module from Ontomir evm extends the standard x/bank module from 6 to 18 decimal precision for EVM compatibility.

For conceptual understanding of precision handling and mathematical proofs, see [Precision Handling](/docs/evm/next/documentation/concepts/precision-handling).

Overview

The module acts as a wrapper around x/bank, providing:

  • 18 decimal precision for EVM (10^18 sub-atomic units)

  • Backward compatibility with 6 decimal Ontomir operations

  • Transparent conversion between precision levels

  • Fractional balance tracking for sub-test amounts

Developed with contributions from the [Kava](https://www.kava.io/) team.

State

The module maintains fractional balances and remainder :

Object
Key
Value
Description

FractionalBalance

0x01 + address

math.Int

Account fractional balance (0 to 10^12-1)

Remainder

0x02

math.Int

Uncirculated fractional amount

Balance Representation

Full balance calculation:

atest_balance = test_balance × 10^12 + fractional_balance

Where:

  • test_balance: Stored in x/bank (6 decimals)

  • fractional_balance: Stored in x/precisebank (0 to 10^12-1)

  • atest_balance: Full 18-decimal precision

Keeper Interface

The module provides a bank-compatible keeper :

type Keeper interface {
    // Query methods
    GetBalance(ctx, addr, denom) sdk.Coin
    SpendableCoins(ctx, addr) sdk.Coins

    // Transfer methods
    SendCoins(ctx, from, to, amt) error
    SendCoinsFromModuleToAccount(ctx, module, to, amt) error
    SendCoinsFromAccountToModule(ctx, from, module, amt) error

    // Mint/Burn methods
    MintCoins(ctx, module, amt) error
    BurnCoins(ctx, module, amt) error
}

Extended Coin Support

Automatic handling of "atest" denomination:

  • Converts between test and atest transparently

  • Maintains fractional balances for sub-test amounts

  • Ensures consistency between x/bank and x/precisebank

Operations

Transfer

Handles both integer and fractional components:

// SendCoins automatically handles precision
keeper.SendCoins(ctx, from, to, sdk.NewCoins(
    sdk.NewCoin("atest", sdk.NewInt(1500000000000)), // 0.0015 test
))

Algorithm:

  1. Subtract from sender (update b(sender) and f(sender))

  2. Add to receiver (update b(receiver) and f(receiver))

  3. Update reserve based on carry/borrow

  4. Remainder unchanged (mathematical guarantee)

Mint

Creates new tokens with proper backing:

// MintCoins with extended precision
keeper.MintCoins(ctx, moduleName, sdk.NewCoins(
    sdk.NewCoin("atest", sdk.NewInt(1000000000000000000)), // 1 test
))

Algorithm:

  1. Add to account (update b(account) and f(account))

  2. Decrease remainder (tokens enter circulation)

  3. Update reserve for consistency

Burn

Removes tokens from circulation:

// BurnCoins with extended precision
keeper.BurnCoins(ctx, moduleName, sdk.NewCoins(
    sdk.NewCoin("atest", sdk.NewInt(500000000000)),  // 0.0005 test
))

Algorithm:

  1. Subtract from account (update b(account) and f(account))

  2. Increase remainder (tokens leave circulation)

  3. Update reserve for consistency

Events

Standard bank events with extended precision amounts:

Transfer Events

Event
Attributes
Description

transfer

sender, recipient, amount

Full atest amount

coin_spent

spender, amount

Extended precision

coin_received

receiver, amount

Extended precision

Mint/Burn Events

Event
Attributes
Description

coinbase

minter, amount

Minted with 18 decimals

burn

burner, amount

Burned with 18 decimals

Queries

gRPC

service Query {
    // Get total of all fractional balances
    rpc TotalFractionalBalances(QueryTotalFractionalBalancesRequest)
        returns (QueryTotalFractionalBalancesResponse);

    // Get current remainder amount
    rpc Remainder(QueryRemainderRequest)
        returns (QueryRemainderResponse);

    // Get fractional balance for an account
    rpc FractionalBalance(QueryFractionalBalanceRequest)
        returns (QueryFractionalBalanceResponse);
}

CLI

```bash Query-Total # Query total fractional balances evmd query precisebank total-fractional-balances

Example output:

total: "2000000000000atest"


```bash Query-Remainder
# Query remainder amount
evmd query precisebank remainder

# Example output:
# remainder: "100atest"
# Query account fractional balance
evmd query precisebank fractional-balance Ontomir1...

# Example output:
# fractional_balance: "10000atest"

Integration

For EVM Module

Replace bank keeper with precisebank keeper in app.go:

app.EvmKeeper = evmkeeper.NewKeeper(
    app.PrecisebankKeeper, // Instead of app.BankKeeper
    // ... other parameters
)

For Other Modules

Query extended balances through standard interface:

// Automatically handles atest denomination
balance := keeper.GetBalance(ctx, addr, "atest")

// Transfer with 18 decimal precision
err := keeper.SendCoins(ctx, from, to, 
    sdk.NewCoins(sdk.NewCoin("atest", amount)))

Reserve Account

The reserve account maintains backing for fractional balances:

Reserve_test × 10^12 = Σ(all fractional balances) + remainder

Monitoring Reserve

# Check reserve account balance
evmd query bank balances Ontomir1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q

# Verify invariant
evmd query precisebank total-fractional-balances
evmd query precisebank remainder

Invariants

Critical invariants maintained by the module:

Invariant
Formula
Description

Supply

Total_atest = Total_test × 10^12 - remainder

Total supply consistency

Fractional Range

0 ≤ f(n) < 10^12

Valid fractional bounds

Reserve Backing

Reserve × 10^12 = Σf(n) + r

Full backing guarantee

Conservation

Δ(Total_atest) = Δ(Total_test × 10^12)

No creation/destruction

Best Practices

Chain Integration

  1. Reserve Monitoring

    • Track reserve balance for validation

    • Set up alerts for invariant violations

    • Regular audits of fractional sums

  2. Migration Path

    // Deploy in passive mode
    app.PrecisebankKeeper = precisebankkeeper.NewKeeper(
        app.BankKeeper,
        // Fractional balances start at zero
    )
  3. Testing

    // Verify fractional operations
    suite.Require().Equal(
        expectedFractional,
        keeper.GetFractionalBalance(ctx, addr),
    )

dApp Development

  1. Balance Queries

    // Query in atest (18 decimals)
    const balance = await queryClient.precisebank.fractionalBalance({
        address: "Ontomir1..."
    });
  2. Precision Handling

    // Convert between precisions
    const testAmount = atestAmount / BigInt(10**12);
    const atestAmount = testAmount * BigInt(10**12);

Security Considerations

Overflow Protection

  • All arithmetic uses checked math

  • Fractional values bounded to [0, 10^12)

  • Integer overflow impossible by design

Atomicity

  • Balance updates are atomic

  • Reserve adjustments in same transaction

  • No intermediate states visible

Precision Guarantees

  • No precision loss during operations

  • All fractional amounts preserved

  • Rounding only at display layer

Performance

Storage Impact

  • Additional O(n) storage for accounts with fractional balances

  • Most accounts have zero fractional balance (no storage)

  • Reserve account: single additional balance

Computation

  • Constant time operations for all transfers

  • Single addition/multiplication for balance queries

  • ~10% gas overhead for fractional updates

Optimization

  • Lazy initialization (fractional balances start at zero)

  • Sparse storage (only non-zero fractions stored)

  • Batch operations maintain efficiency

Troubleshooting

Common Issues

Issue
Cause
Solution

"fractional overflow"

Fractional > 10^12

Check calculation logic

"insufficient balance"

Including fractional

Verify full atest balance

"invariant violation"

Supply mismatch

Audit reserve and remainder

Validation Commands

# Verify module invariants
evmd query precisebank total-fractional-balances
evmd query precisebank remainder

# Check specific account
evmd query bank balances Ontomir1... --denom test
evmd query precisebank fractional-balance Ontomir1...

最后更新于