-
Notifications
You must be signed in to change notification settings - Fork 153
feat(vm): support non-18-decimal evm_denom with x/precisebank #926
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
luchenqun
wants to merge
2
commits into
cosmos:main
Choose a base branch
from
luchenqun:feat/non-18-decimal-chain
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds support for using non-18-decimal denominations (e.g.,
utestwith 6 decimals) asevm_denom, enabling proper testing and usage of thex/precisebankmodule for precision extension.Changes
1.
local_node.shAdded
--denom-utestflag to configure the local node with a 6-decimalevm_denom:Configuration differences:
--denom-utest(6-decimal)evm_denomutestatestextended_denomatestdenom_unitsbase_feeGenesis 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_denomandextended_denom_options:{ "evm": { "params": { "evm_denom": "utest", "extended_denom_options": { "extended_denom": "atest" } } } }2.
x/vm/keeper/coin_info.goRefactored
LoadEvmCoinInfoto support non-18-decimal chains:evm_denomfirst, then fallback toextended_denomevm_denom's exponent fromdenom_unitsevm_denomexp=0: it's the base, use it asextended_denom, decimals=18evm_denomexp>0: requireextended_denom_options, use providedextended_denomHow x/precisebank Works
The EVM requires 18-decimal precision. For chains with non-18-decimal coins (e.g., 6-decimal
uatom),x/precisebankextends precision:Where:
ExtendedBalance: 18-decimal balance inextended_denom(e.g., "atest")IntegerBalance: balance inevm_denomstored in x/bank (e.g., "utest")FractionalBalance: remainder stored in x/precisebank (0 ≤ f < ConversionFactor)ConversionFactor = 10^(18 - decimals)= 10^12 for 6-decimal coinsTesting
The test script includes 6 test cases to verify
x/precisebankfractional balance handling:Each test case verifies:
Related Modules
x/precisebank: Handles fractional balance storage and precision extensionx/vm: UsesEvmCoinInfofor EVM coin configurationx/bank: Stores integer balances and denom metadata