Thanks to visit codestin.com
Credit goes to Github.com

Skip to content

Conversation

@luchenqun
Copy link

Summary

This PR adds support for using non-18-decimal denominations (e.g., utest with 6 decimals) as evm_denom, enabling proper testing and usage of the x/precisebank module for precision extension.

Changes

1. local_node.sh

Added --denom-utest flag to configure the local node with a 6-decimal evm_denom:

# Start local node with utest (6-decimal) configuration
bash local_node.sh --denom-utest -y

# Start local node with atest (18-decimal) configuration (default)
bash local_node.sh -y

Configuration differences:

Config --denom-utest (6-decimal) Default (18-decimal)
evm_denom utest atest
extended_denom atest N/A
denom_units atest(exp=0), utest(exp=6), test(exp=18) atest(exp=0), test(exp=18)
base_fee 0 (for easier balance verification) default

Genesis Configuration Example (--denom-utest):

Bank module - denom_metadata:

{
  "denom_metadata": [
    {
      "description": "The native staking token for evmd.",
      "denom_units": [
        { "denom": "atest", "exponent": 0, "aliases": ["attotest"] },
        { "denom": "utest", "exponent": 6, "aliases": [] },
        { "denom": "test", "exponent": 18, "aliases": [] }
      ],
      "base": "atest",
      "display": "test",
      "name": "Test Token",
      "symbol": "TEST"
    }
  ]
}

EVM module - evm_denom and extended_denom_options:

{
  "evm": {
    "params": {
      "evm_denom": "utest",
      "extended_denom_options": {
        "extended_denom": "atest"
      }
    }
  }
}

2. x/vm/keeper/coin_info.go

Refactored LoadEvmCoinInfo to support non-18-decimal chains:

  • Metadata lookup: Try evm_denom first, then fallback to extended_denom
  • Decimals calculation: Use evm_denom's exponent from denom_units
  • Extended denom resolution:
    • If evm_denom exp=0: it's the base, use it as extended_denom, decimals=18
    • If evm_denom exp>0: require extended_denom_options, use provided extended_denom

How x/precisebank Works

The EVM requires 18-decimal precision. For chains with non-18-decimal coins (e.g., 6-decimal uatom), x/precisebank extends precision:

ExtendedBalance(n) = IntegerBalance(n) × ConversionFactor + FractionalBalance(n)

Where:

  • ExtendedBalance: 18-decimal balance in extended_denom (e.g., "atest")
  • IntegerBalance: balance in evm_denom stored in x/bank (e.g., "utest")
  • FractionalBalance: remainder stored in x/precisebank (0 ≤ f < ConversionFactor)
  • ConversionFactor = 10^(18 - decimals) = 10^12 for 6-decimal coins

Testing

# 1. Start local node with utest configuration
bash local_node.sh --denom-utest -y

# 2. Run test script (see test_precisebank.js at the end of this PR)
node test_precisebank.js

The test script includes 6 test cases to verify x/precisebank fractional balance handling:

Test Case Description
Test 1 dev0 sends 1 atest to dev1
Test 2 dev1 sends 1 atest to dev0
Test 3 dev0 sends 1 test (10^18 atest) to dev1
Test 4 dev1 sends 1 test to dev0
Test 5 dev0 sends 1 test + 1 atest to dev1
Test 6 dev1 sends 1 test + 1 atest to dev0

Each test case verifies:

  • Bank module: utest balance of dev0, dev1, and precisebank module account
  • Precisebank module: fractional balance (atest) of dev0 and dev1

Related Modules

  • x/precisebank: Handles fractional balance storage and precision extension
  • x/vm: Uses EvmCoinInfo for EVM coin configuration
  • x/bank: Stores integer balances and denom metadata

@luchenqun
Copy link
Author

test_precisebank.js

/**
 * Test script for x/precisebank with utest (6-decimal) evm_denom configuration
 *
 * Prerequisites:
 *   1. Start local node with utest configuration:
 *      bash local_node.sh --denom-utest -y
 *   2. Run this test:
 *      node test_precisebank.js
 *
 * Denom configuration:
 *   - atest (exp=0): extended_denom, 18 decimals, 1 atest = 1 wei
 *   - utest (exp=6): evm_denom, 6 decimals, 1 utest = 10^12 atest
 *   - test (exp=18): display denom, 1 test = 10^18 atest
 *
 * ConversionFactor = 10^12 (to convert utest to atest)
 */

import { ethers } from 'ethers';

const RPC_URL = 'http://localhost:8545';
const REST_URL = 'http://localhost:1317';

// Dev accounts from local_node.sh
// dev0: 0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101 | cosmos1cml96vmptgw99syqrrz8az79xer2pcgp95srxm
const DEV0_PRIVATE_KEY = '0x88cbead91aee890d27bf06e003ade3d4e952427e88f88d31d61d3ef5e5d54305';
const DEV0_BECH32 = 'cosmos1cml96vmptgw99syqrrz8az79xer2pcgp95srxm';

// dev1: 0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17 | cosmos1jcltmuhplrdcwp7stlr4hlhlhgd4htqhnu0t2g
const DEV1_PRIVATE_KEY = '0x741de4f8988ea941d3ff0287911ca4074e62b7d45c991a51186455366f10b544';
const DEV1_BECH32 = 'cosmos1jcltmuhplrdcwp7stlr4hlhlhgd4htqhnu0t2g';

// precisebank module account: cosmos12yfe2jaupmtjruwxsec7hg7er60fhaa4qh25lc | 0x5113954bbC0eD721F1C68671EBa3d91e9e9bF7b5
const PRECISEBANK_MODULE_BECH32 = 'cosmos12yfe2jaupmtjruwxsec7hg7er60fhaa4qh25lc';

// Constants
const CONVERSION_FACTOR = BigInt(10 ** 12); // 1 utest = 10^12 atest
const ONE_WEI = BigInt(1);
const ONE_ETH = BigInt(10 ** 18); // 1 test = 10^18 atest = 10^6 utest

// ==================== Query Functions ====================

async function queryBankBalance(bech32Addr, denom = 'utest') {
  try {
    const res = await fetch(`${REST_URL}/cosmos/bank/v1beta1/balances/${bech32Addr}/by_denom?denom=${denom}`);
    const data = await res.json();
    return BigInt(data.balance?.amount || '0');
  } catch (e) {
    console.error(`Failed to query bank balance: ${e.message}`);
    return BigInt(0);
  }
}

async function queryFractionalBalance(bech32Addr) {
  try {
    const res = await fetch(`${REST_URL}/cosmos/evm/precisebank/v1/fractional_balance/${bech32Addr}`);
    const data = await res.json();
    return BigInt(data.fractional_balance?.amount || '0');
  } catch (e) {
    console.error(`Failed to query fractional balance: ${e.message}`);
    return BigInt(0);
  }
}

async function queryRemainder() {
  try {
    const res = await fetch(`${REST_URL}/cosmos/evm/precisebank/v1/remainder`);
    const data = await res.json();
    return BigInt(data.remainder?.amount || '0');
  } catch (e) {
    console.error(`Failed to query remainder: ${e.message}`);
    return BigInt(0);
  }
}

// ==================== State Tracking ====================

class State {
  constructor() {
    this.dev0_utest = BigInt(0);
    this.dev1_utest = BigInt(0);
    this.module_utest = BigInt(0);
    this.dev0_fractional = BigInt(0);
    this.dev1_fractional = BigInt(0);
    this.remainder = BigInt(0);
  }

  async fetch() {
    this.dev0_utest = await queryBankBalance(DEV0_BECH32, 'utest');
    this.dev1_utest = await queryBankBalance(DEV1_BECH32, 'utest');
    this.module_utest = await queryBankBalance(PRECISEBANK_MODULE_BECH32, 'utest');
    this.dev0_fractional = await queryFractionalBalance(DEV0_BECH32);
    this.dev1_fractional = await queryFractionalBalance(DEV1_BECH32);
    this.remainder = await queryRemainder();
  }

  print(label) {
    console.log(`\n--- ${label} ---`);
    console.log(`[Bank Module - utest]`);
    console.log(`  dev0: ${this.dev0_utest.toString()} utest`);
    console.log(`  dev1: ${this.dev1_utest.toString()} utest`);
    console.log(`  precisebank module: ${this.module_utest.toString()} utest`);
    console.log(`[Precisebank Module - fractional balance (atest)]`);
    console.log(`  dev0: ${this.dev0_fractional.toString()} atest`);
    console.log(`  dev1: ${this.dev1_fractional.toString()} atest`);
    console.log(`  remainder: ${this.remainder.toString()} atest`);
  }

  // Get total balance in atest for an account
  getTotalAtest(who) {
    if (who === 'dev0') {
      return this.dev0_utest * CONVERSION_FACTOR + this.dev0_fractional;
    } else if (who === 'dev1') {
      return this.dev1_utest * CONVERSION_FACTOR + this.dev1_fractional;
    }
    return BigInt(0);
  }
}

// ==================== Verification ====================

function verify(actual, expected, name) {
  if (actual === expected) {
    console.log(`  ✅ ${name}: ${actual.toString()} (expected: ${expected.toString()})`);
    return true;
  } else {
    console.log(`  ❌ ${name}: ${actual.toString()} (expected: ${expected.toString()})`);
    return false;
  }
}

function verifyState(state, expected, label) {
  console.log(`\n--- Verify: ${label} ---`);
  let allPassed = true;
  allPassed &= verify(state.dev0_utest, expected.dev0_utest, 'dev0 utest');
  allPassed &= verify(state.dev1_utest, expected.dev1_utest, 'dev1 utest');
  allPassed &= verify(state.module_utest, expected.module_utest, 'module utest');
  allPassed &= verify(state.dev0_fractional, expected.dev0_fractional, 'dev0 fractional');
  allPassed &= verify(state.dev1_fractional, expected.dev1_fractional, 'dev1 fractional');
  return allPassed;
}

// ==================== Test Cases ====================

async function sendTx(wallet, to, value) {
  const tx = await wallet.sendTransaction({
    to: to,
    value: value,
    gasPrice: 0, // Set gasPrice to 0 for easier testing
  });
  console.log(`  TX Hash: ${tx.hash}`);
  await tx.wait();
  console.log(`  Transaction confirmed!`);
}

async function main() {
  console.log('='.repeat(60));
  console.log('x/precisebank Test for utest (6-decimal) evm_denom');
  console.log('='.repeat(60));

  // Setup provider and wallets
  const provider = new ethers.JsonRpcProvider(RPC_URL);
  const dev0 = new ethers.Wallet(DEV0_PRIVATE_KEY, provider);
  const dev1 = new ethers.Wallet(DEV1_PRIVATE_KEY, provider);

  console.log(`\ndev0: ${dev0.address} (${DEV0_BECH32})`);
  console.log(`dev1: ${dev1.address} (${DEV1_BECH32})`);
  console.log(`precisebank module: ${PRECISEBANK_MODULE_BECH32}`);
  console.log(`\nConversionFactor: ${CONVERSION_FACTOR.toString()} (1 utest = 10^12 atest)`);

  // Fetch initial state
  const state = new State();
  await state.fetch();
  state.print('Initial State');

  const initial_dev0_utest = state.dev0_utest;
  const initial_dev1_utest = state.dev1_utest;

  let allTestsPassed = true;

  // ==================== Test Case 1: dev0 sends 1 wei to dev1 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 1: dev0 sends 1 wei (1 atest) to dev1');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - dev0 utest: -1 (need to borrow 1 utest for fractional)');
  console.log('  - dev0 fractional: 10^12 - 1 = 999999999999 atest');
  console.log('  - dev1 utest: unchanged');
  console.log('  - dev1 fractional: +1 atest');
  console.log('  - module utest: +1 (reserve for fractional balances)');

  await sendTx(dev0, dev1.address, ONE_WEI);
  await state.fetch();
  state.print('After Test Case 1');

  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest - BigInt(1),
      dev1_utest: initial_dev1_utest,
      module_utest: BigInt(1),
      dev0_fractional: CONVERSION_FACTOR - BigInt(1), // 999999999999
      dev1_fractional: BigInt(1),
    },
    'Test Case 1'
  );

  // ==================== Test Case 2: dev1 sends 1 wei to dev0 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 2: dev1 sends 1 wei (1 atest) to dev0');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - dev0 fractional + 1 = 10^12, carry to utest');
  console.log('  - dev0 utest: +1 (from carry)');
  console.log('  - dev0 fractional: 0');
  console.log('  - dev1 fractional: 0 (needs borrow but has 1, so 1-1=0)');
  console.log('  - dev1 utest: unchanged (no borrow needed since fractional covers it)');
  console.log('  - module utest: -1 (release reserve)');

  await sendTx(dev1, dev0.address, ONE_WEI);
  await state.fetch();
  state.print('After Test Case 2');

  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest, // restored
      dev1_utest: initial_dev1_utest, // unchanged
      module_utest: BigInt(0), // released
      dev0_fractional: BigInt(0),
      dev1_fractional: BigInt(0),
    },
    'Test Case 2'
  );

  // ==================== Test Case 3: dev0 sends 1 test (10^18 atest = 10^6 utest) to dev1 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 3: dev0 sends 1 test (10^18 atest = 10^6 utest) to dev1');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - dev0 utest: -10^6');
  console.log('  - dev1 utest: +10^6');
  console.log('  - No fractional changes (exact utest amount)');

  await sendTx(dev0, dev1.address, ONE_ETH);
  await state.fetch();
  state.print('After Test Case 3');

  const one_test_in_utest = BigInt(10 ** 6);
  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest - one_test_in_utest,
      dev1_utest: initial_dev1_utest + one_test_in_utest,
      module_utest: BigInt(0),
      dev0_fractional: BigInt(0),
      dev1_fractional: BigInt(0),
    },
    'Test Case 3'
  );

  // ==================== Test Case 4: dev1 sends 1 test to dev0 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 4: dev1 sends 1 test (10^18 atest = 10^6 utest) to dev0');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - Restore to initial state');

  await sendTx(dev1, dev0.address, ONE_ETH);
  await state.fetch();
  state.print('After Test Case 4');

  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest,
      dev1_utest: initial_dev1_utest,
      module_utest: BigInt(0),
      dev0_fractional: BigInt(0),
      dev1_fractional: BigInt(0),
    },
    'Test Case 4'
  );

  // ==================== Test Case 5: dev0 sends 1 test + 1 wei to dev1 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 5: dev0 sends 1 test + 1 wei (10^18 + 1 atest) to dev1');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - dev0 utest: -(10^6 + 1) = need extra 1 utest for fractional borrow');
  console.log('  - dev0 fractional: 10^12 - 1 = 999999999999');
  console.log('  - dev1 utest: +10^6');
  console.log('  - dev1 fractional: +1');
  console.log('  - module utest: +1');

  await sendTx(dev0, dev1.address, ONE_ETH + ONE_WEI);
  await state.fetch();
  state.print('After Test Case 5');

  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest - one_test_in_utest - BigInt(1),
      dev1_utest: initial_dev1_utest + one_test_in_utest,
      module_utest: BigInt(1),
      dev0_fractional: CONVERSION_FACTOR - BigInt(1),
      dev1_fractional: BigInt(1),
    },
    'Test Case 5'
  );

  // ==================== Test Case 6: dev1 sends 1 test + 1 wei to dev0 ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Case 6: dev1 sends 1 test + 1 wei (10^18 + 1 atest) to dev0');
  console.log('='.repeat(60));
  console.log('\nExpected changes:');
  console.log('  - Restore to initial state');

  await sendTx(dev1, dev0.address, ONE_ETH + ONE_WEI);
  await state.fetch();
  state.print('After Test Case 6');

  allTestsPassed &= verifyState(
    state,
    {
      dev0_utest: initial_dev0_utest,
      dev1_utest: initial_dev1_utest,
      module_utest: BigInt(0),
      dev0_fractional: BigInt(0),
      dev1_fractional: BigInt(0),
    },
    'Test Case 6'
  );

  // ==================== Summary ====================
  console.log('\n' + '='.repeat(60));
  console.log('Test Summary');
  console.log('='.repeat(60));
  if (allTestsPassed) {
    console.log('\n✅ All tests passed!');
  } else {
    console.log('\n❌ Some tests failed!');
  }
}

main().catch(console.error);

Log for run node test_precisebank.js

============================================================
x/precisebank Test for utest (6-decimal) evm_denom
============================================================

dev0: 0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101 (cosmos1cml96vmptgw99syqrrz8az79xer2pcgp95srxm)
dev1: 0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17 (cosmos1jcltmuhplrdcwp7stlr4hlhlhgd4htqhnu0t2g)
precisebank module: cosmos12yfe2jaupmtjruwxsec7hg7er60fhaa4qh25lc

ConversionFactor: 1000000000000 (1 utest = 10^12 atest)

--- Initial State ---
[Bank Module - utest]
  dev0: 1000000000000000000000 utest
  dev1: 1000000000000000000000 utest
  precisebank module: 0 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 0 atest
  dev1: 0 atest
  remainder: 0 atest

============================================================
Test Case 1: dev0 sends 1 wei (1 atest) to dev1
============================================================

Expected changes:
  - dev0 utest: -1 (need to borrow 1 utest for fractional)
  - dev0 fractional: 10^12 - 1 = 999999999999 atest
  - dev1 utest: unchanged
  - dev1 fractional: +1 atest
  - module utest: +1 (reserve for fractional balances)
  TX Hash: 0xb137d171587ba252f0942286619ba022a9f339336f4d1916e642cb440dc0b502
  Transaction confirmed!

--- After Test Case 1 ---
[Bank Module - utest]
  dev0: 999999999999999999999 utest
  dev1: 1000000000000000000000 utest
  precisebank module: 1 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 999999999999 atest
  dev1: 1 atest
  remainder: 0 atest

--- Verify: Test Case 1 ---
  ✅ dev0 utest: 999999999999999999999 (expected: 999999999999999999999)
  ✅ dev1 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ module utest: 1 (expected: 1)
  ✅ dev0 fractional: 999999999999 (expected: 999999999999)
  ✅ dev1 fractional: 1 (expected: 1)

============================================================
Test Case 2: dev1 sends 1 wei (1 atest) to dev0
============================================================

Expected changes:
  - dev0 fractional + 1 = 10^12, carry to utest
  - dev0 utest: +1 (from carry)
  - dev0 fractional: 0
  - dev1 fractional: 0 (needs borrow but has 1, so 1-1=0)
  - dev1 utest: unchanged (no borrow needed since fractional covers it)
  - module utest: -1 (release reserve)
  TX Hash: 0xe41fd494b6d5a69dc3ba20e72392f09820ac7b3d59e690e9a383f72f2d0dd5fd
  Transaction confirmed!

--- After Test Case 2 ---
[Bank Module - utest]
  dev0: 1000000000000000000000 utest
  dev1: 1000000000000000000000 utest
  precisebank module: 0 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 0 atest
  dev1: 0 atest
  remainder: 0 atest

--- Verify: Test Case 2 ---
  ✅ dev0 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ dev1 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ module utest: 0 (expected: 0)
  ✅ dev0 fractional: 0 (expected: 0)
  ✅ dev1 fractional: 0 (expected: 0)

============================================================
Test Case 3: dev0 sends 1 test (10^18 atest = 10^6 utest) to dev1
============================================================

Expected changes:
  - dev0 utest: -10^6
  - dev1 utest: +10^6
  - No fractional changes (exact utest amount)
  TX Hash: 0x3d0ee31aae3d7db5b9f404017823f79fb6fc45f71dafbe6199972eeab1bd14e5
  Transaction confirmed!

--- After Test Case 3 ---
[Bank Module - utest]
  dev0: 999999999999999000000 utest
  dev1: 1000000000000001000000 utest
  precisebank module: 0 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 0 atest
  dev1: 0 atest
  remainder: 0 atest

--- Verify: Test Case 3 ---
  ✅ dev0 utest: 999999999999999000000 (expected: 999999999999999000000)
  ✅ dev1 utest: 1000000000000001000000 (expected: 1000000000000001000000)
  ✅ module utest: 0 (expected: 0)
  ✅ dev0 fractional: 0 (expected: 0)
  ✅ dev1 fractional: 0 (expected: 0)

============================================================
Test Case 4: dev1 sends 1 test (10^18 atest = 10^6 utest) to dev0
============================================================

Expected changes:
  - Restore to initial state
  TX Hash: 0xd67bb8c84307692747f11bf57da07e038c0ff393bda59b343e2714acf48d8014
  Transaction confirmed!

--- After Test Case 4 ---
[Bank Module - utest]
  dev0: 1000000000000000000000 utest
  dev1: 1000000000000000000000 utest
  precisebank module: 0 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 0 atest
  dev1: 0 atest
  remainder: 0 atest

--- Verify: Test Case 4 ---
  ✅ dev0 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ dev1 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ module utest: 0 (expected: 0)
  ✅ dev0 fractional: 0 (expected: 0)
  ✅ dev1 fractional: 0 (expected: 0)

============================================================
Test Case 5: dev0 sends 1 test + 1 wei (10^18 + 1 atest) to dev1
============================================================

Expected changes:
  - dev0 utest: -(10^6 + 1) = need extra 1 utest for fractional borrow
  - dev0 fractional: 10^12 - 1 = 999999999999
  - dev1 utest: +10^6
  - dev1 fractional: +1
  - module utest: +1
  TX Hash: 0x10c57697c5931790930198a48dfb92ba6e02af6ed52d22c849003086a5011271
  Transaction confirmed!

--- After Test Case 5 ---
[Bank Module - utest]
  dev0: 999999999999998999999 utest
  dev1: 1000000000000001000000 utest
  precisebank module: 1 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 999999999999 atest
  dev1: 1 atest
  remainder: 0 atest

--- Verify: Test Case 5 ---
  ✅ dev0 utest: 999999999999998999999 (expected: 999999999999998999999)
  ✅ dev1 utest: 1000000000000001000000 (expected: 1000000000000001000000)
  ✅ module utest: 1 (expected: 1)
  ✅ dev0 fractional: 999999999999 (expected: 999999999999)
  ✅ dev1 fractional: 1 (expected: 1)

============================================================
Test Case 6: dev1 sends 1 test + 1 wei (10^18 + 1 atest) to dev0
============================================================

Expected changes:
  - Restore to initial state
  TX Hash: 0x09360493db33b54fd1af8df0b0996df73aa0b852e2b78e964d7e1ba62d11e93b
  Transaction confirmed!

--- After Test Case 6 ---
[Bank Module - utest]
  dev0: 1000000000000000000000 utest
  dev1: 1000000000000000000000 utest
  precisebank module: 0 utest
[Precisebank Module - fractional balance (atest)]
  dev0: 0 atest
  dev1: 0 atest
  remainder: 0 atest

--- Verify: Test Case 6 ---
  ✅ dev0 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ dev1 utest: 1000000000000000000000 (expected: 1000000000000000000000)
  ✅ module utest: 0 (expected: 0)
  ✅ dev0 fractional: 0 (expected: 0)
  ✅ dev1 fractional: 0 (expected: 0)

============================================================
Test Summary
============================================================

✅ All tests passed!

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.

1 participant