Skip to content

Cleanup/account id numeric#19

Merged
tac0turtle merged 7 commits intomainfrom
cleanup/account-id-numeric
Feb 28, 2026
Merged

Cleanup/account id numeric#19
tac0turtle merged 7 commits intomainfrom
cleanup/account-id-numeric

Conversation

@tac0turtle
Copy link
Contributor

@tac0turtle tac0turtle commented Feb 27, 2026

Overview

Summary by CodeRabbit

  • Refactor

    • Standardized account identifier construction across the codebase and tests.
    • Updated many modules to use the new AccountId representation and helpers.
  • New Features

    • Genesis/config formats now accept and emit hex-encoded account identifiers.
    • Genesis config field for the minter ID now uses a 64-bit numeric shape (affects JSON shape).

tac0turtle and others added 4 commits February 27, 2026 16:45
Replace all AccountId::new(N) usages in the 5 assigned core SDK files
with the equivalent AccountId::from_bytes([...]) byte array literals:

- storage_api.rs: STORAGE_ACCOUNT_ID constant (N=1)
- unique_api.rs: UNIQUE_HANDLER_ACCOUNT_ID constant (N=5)
- core/lib.rs: inline test AccountIds for 1, 2, 10, 42; updated
  test_account_id_u128_compat to assert on bytes instead of inner()
- fungible_asset.rs: test AccountIds 1, 1111, 1234, 2222, 9999
  converted to named const byte arrays
- collections/mocks.rs: MockEnvironment::new keeps u128 params but
  constructs AccountId via from_bytes instead of AccountId::new

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, and binaries

Replaces all AccountId::new(N: u128) calls with AccountId::from_bytes([u8;32])
across: chain-index integration tests, rpc/types log tests, evnode run_server
example, debugger (lib, replay, trace, breakpoints, inspector), storage warming
tests, testapp lib constants (MINTER, PLACEHOLDER_ACCOUNT), testapp/evd
run_custom_genesis minter_id runtime conversion, mempool_e2e tests, and txload
test. The minter_id config field remains u128 (JSON-deserialized) with an inline
conversion at the call site.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… standards

Replace all AccountId::new(N: u128) calls with AccountId::from_bytes([u8;32])
across testing, authentication, fungible_asset standards, and token, scheduler,
nonceless_account extensions. Also change setup_token() and setup_account()
helper signatures from u128 params to AccountId to avoid internal new() usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Completes TODO(account-id-cleanup). All callers have been migrated to
AccountId::from_bytes([u8;32]) in the preceding commits. The two
remaining string-fallback paths in genesis/file.rs now parse
hex-encoded 32-byte account IDs instead of decimal u128 strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6cc2fb and 033eb4b.

📒 Files selected for processing (6)
  • bin/evd/src/main.rs
  • bin/testapp/src/genesis_config.rs
  • bin/testapp/src/main.rs
  • crates/app/genesis/src/file.rs
  • crates/app/sdk/collections/src/mocks.rs
  • crates/app/stf/src/runtime_api_impl.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/app/stf/src/runtime_api_impl.rs

📝 Walkthrough

Walkthrough

Refactors AccountId usage across the codebase: adds AccountId::from_u64(u64), removes the legacy AccountId::new(u128) and inner() methods, updates constants and genesis types to use AccountId, and changes SenderSpec/RecipientSpec to wrap AccountId with hex-based JSON (de)serialization. Numerous tests and helpers updated accordingly.

Changes

Cohort / File(s) Summary
Core AccountId API
crates/app/sdk/core/src/lib.rs
Add AccountId::from_u64(u64); remove AccountId::new(u128) and inner(); update tests to use from_u64.
Genesis model & serialization
crates/app/genesis/src/file.rs, crates/app/genesis/src/lib.rs, crates/app/genesis/src/types.rs, crates/app/genesis/Cargo.toml
Replace u128 IDs with AccountId in SenderSpec/RecipientSpec; add Serialize/Deserialize implementations using hex-encoded 32-byte IDs; update genesis resolution to use AccountId and increase(); add hex = "0.4" dependency.
Constants & config
crates/app/sdk/core/src/runtime_api.rs, crates/app/sdk/core/src/storage_api.rs, crates/app/sdk/core/src/unique_api.rs, crates/app/genesis/src/lib.rs, bin/testapp/src/genesis_config.rs, bin/testapp/src/lib.rs
Migrate public constants and genesis config field minter_id: u128 -> u64; update constant initializers to from_u64(...).
Mocks, test helpers, and utilities
crates/app/sdk/collections/src/mocks.rs, crates/app/sdk/core/src/fungible_asset.rs, crates/app/sdk/testing/src/lib.rs, crates/app/sdk/core/src/lib.rs (tests), crates/app/sdk/extensions/token/src/lib.rs
Change mock/test helper signatures to accept AccountId or u64; introduce precomputed AccountId test constants; update setup_token and other helpers to use AccountId::from_u64.
SDK extensions & standards tests
crates/app/sdk/extensions/nonceless_account/src/lib.rs, crates/app/sdk/extensions/scheduler/src/lib.rs, crates/app/sdk/extensions/token/src/lib.rs, crates/app/sdk/standards/*
Replace AccountId::new(...) with AccountId::from_u64(...) across extension and standards test suites; pass concrete AccountId where appropriate.
STF / runtime / tx libs
crates/app/stf/src/lib.rs, crates/app/stf/src/runtime_api_impl.rs, crates/app/tx/eth/src/traits.rs
Update initial account constants and next-account logic to use from_u64 and rename internal constants; minor test assertion additions.
Other tests, RPC, examples, storage, debugger
crates/rpc/chain-index/src/integration.rs, crates/rpc/types/src/log.rs, crates/rpc/evnode/examples/run_server.rs, crates/storage/src/warming.rs, crates/testing/debugger/src/*, bin/evd/src/main.rs, bin/testapp/src/main.rs, bin/txload/src/main.rs, bin/testapp/tests/mempool_e2e.rs
Systematic replacement of AccountId construction to from_u64(...) in tests, examples, and small binaries; genesis minter/account creation adjusted to use from_u64.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I nibble bytes and stitch them new,
from_u64 makes tidy shoes,
Hex crumbs glitter in JSON light,
Tests hop clean from left to right,
A rabbit cheers: IDs snug and true 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Cleanup/account id numeric' uses vague terminology that doesn't clearly convey the main changes (replacing AccountId::new with from_u64, type changes, etc.). Consider using a more descriptive title like 'Replace AccountId::new with from_u64 constructor' or 'Update AccountId construction API across codebase' to better reflect the primary changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 90.70% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cleanup/account-id-numeric

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

tac0turtle and others added 2 commits February 27, 2026 17:09
Add AccountId::from_u64(n: u64) const fn that encodes n in the last 8
bytes big-endian. Replace all byte literal patterns across the workspace
with the cleaner from_u64(N) form. PLACEHOLDER_ACCOUNT migrated from
u128::MAX to u64::MAX (still a never-real-account sentinel).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates raw 32-byte array literals from genesis spec types by storing
AccountId directly. Simplifies deserializers to use from_u64 and adds
manual Serialize impls to hex-encode the account ID.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tac0turtle tac0turtle marked this pull request as ready for review February 27, 2026 19:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
crates/app/stf/src/runtime_api_impl.rs (1)

41-41: Prefer a named constant instead of hard-coding 65535.

Line 41 introduces a magic seed value. A constant makes the runtime account-number baseline explicit and safer to maintain.

♻️ Suggested cleanup
+const INITIAL_ACCOUNT_NUMBER_SEED: u64 = 65_535;
+
 pub(crate) fn next_account_number<S: ReadonlyKV>(
     storage: &mut ExecutionState<S>,
 ) -> SdkResult<AccountId> {
@@
     let last = storage
         .get(&key)?
         .map(|msg| msg.get())
-        .unwrap_or(Ok(AccountId::from_u64(65535)))?;
+        .unwrap_or(Ok(AccountId::from_u64(INITIAL_ACCOUNT_NUMBER_SEED)))?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/app/stf/src/runtime_api_impl.rs` at line 41, The hard-coded seed 65535
should be replaced with a named constant to make the runtime account baseline
explicit: define a constant (e.g., RUNTIME_ACCOUNT_BASE: u64 = 65535) near the
top of the module (or as pub(crate) if needed) and use it in the expression
currently calling AccountId::from_u64(65535) so the line becomes
unwrap_or(Ok(AccountId::from_u64(RUNTIME_ACCOUNT_BASE)))?; keep the constant
name descriptive and update any related comments to reflect the new constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/evd/src/main.rs`:
- Around line 658-662: The code builds `minter` by manual byte-copy from
`genesis_config.minter_id` which is u128 and can silently overflow the intended
u64 account space; replace that logic by validating `genesis_config.minter_id`
fits in u64 and then construct the AccountId via `AccountId::from_u64(...)`
(e.g., check `minter_id <= u64::MAX` and cast to u64 or return/panic on
out-of-range), removing the manual
`b[16..32].copy_from_slice(...)`/`AccountId::from_bytes` approach so the value
is normalized and bounded consistently with other usages like `const MINTER:
AccountId = AccountId::from_u64(100002)`.

In `@crates/app/sdk/collections/src/mocks.rs`:
- Around line 31-42: The mock constructor new currently accepts u128 and
manually packs bytes into AccountId; change its signature to accept u64 for both
account_id and sender_id, and replace the manual byte construction with
AccountId::from_u64(account_id) and AccountId::from_u64(sender_id) respectively
(update any uses inside the new function where AccountId::from_bytes is used).
Ensure any callers are updated to pass u64 if needed and keep the rest of the
struct initialization unchanged.

---

Nitpick comments:
In `@crates/app/stf/src/runtime_api_impl.rs`:
- Line 41: The hard-coded seed 65535 should be replaced with a named constant to
make the runtime account baseline explicit: define a constant (e.g.,
RUNTIME_ACCOUNT_BASE: u64 = 65535) near the top of the module (or as pub(crate)
if needed) and use it in the expression currently calling
AccountId::from_u64(65535) so the line becomes
unwrap_or(Ok(AccountId::from_u64(RUNTIME_ACCOUNT_BASE)))?; keep the constant
name descriptive and update any related comments to reflect the new constant.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 113efeb and a6cc2fb.

📒 Files selected for processing (33)
  • bin/evd/src/main.rs
  • bin/testapp/src/lib.rs
  • bin/testapp/src/main.rs
  • bin/testapp/tests/mempool_e2e.rs
  • bin/txload/src/main.rs
  • crates/app/genesis/Cargo.toml
  • crates/app/genesis/src/file.rs
  • crates/app/genesis/src/lib.rs
  • crates/app/genesis/src/types.rs
  • crates/app/sdk/collections/src/mocks.rs
  • crates/app/sdk/core/src/fungible_asset.rs
  • crates/app/sdk/core/src/lib.rs
  • crates/app/sdk/core/src/runtime_api.rs
  • crates/app/sdk/core/src/storage_api.rs
  • crates/app/sdk/core/src/unique_api.rs
  • crates/app/sdk/extensions/nonceless_account/src/lib.rs
  • crates/app/sdk/extensions/scheduler/src/lib.rs
  • crates/app/sdk/extensions/token/src/lib.rs
  • crates/app/sdk/standards/evolve_authentication/src/lib.rs
  • crates/app/sdk/standards/evolve_fungible_asset/src/lib.rs
  • crates/app/sdk/testing/src/lib.rs
  • crates/app/stf/src/lib.rs
  • crates/app/stf/src/runtime_api_impl.rs
  • crates/app/tx/eth/src/traits.rs
  • crates/rpc/chain-index/src/integration.rs
  • crates/rpc/evnode/examples/run_server.rs
  • crates/rpc/types/src/log.rs
  • crates/storage/src/warming.rs
  • crates/testing/debugger/src/breakpoints.rs
  • crates/testing/debugger/src/inspector.rs
  • crates/testing/debugger/src/lib.rs
  • crates/testing/debugger/src/replay.rs
  • crates/testing/debugger/src/trace.rs

Comment on lines 658 to 662
let minter = {
let mut b = [0u8; 32];
b[16..32].copy_from_slice(&genesis_config.minter_id.to_be_bytes());
AccountId::from_bytes(b)
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the genesis_config.rs file to see the type definition
fd genesis_config.rs

Repository: evstack/ev-rs

Length of output: 90


🏁 Script executed:

# Let's also check the structure of the codebase
git ls-files | grep -E "(genesis|account)" | head -20

Repository: evstack/ev-rs

Length of output: 493


🏁 Script executed:

# Run the verification script from the review comment
fd genesis_config.rs -x rg -n "struct EvdGenesisConfig|minter_id"
rg -n "minter_id\.to_be_bytes|AccountId::from_bytes\(|AccountId::from_u64\(" \
  bin/evd/src/main.rs \
  bin/testapp/src/main.rs \
  bin/testapp/src/lib.rs 2>/dev/null || true

Repository: evstack/ev-rs

Length of output: 977


🏁 Script executed:

# Let's also check the AccountId definition and available constructors
rg -A 5 "impl AccountId|fn from_u64|fn from_bytes" --type rs | head -50

Repository: evstack/ev-rs

Length of output: 83


🏁 Script executed:

rg -B2 -A10 "struct AccountId|impl AccountId" --type-list | head -5

Repository: evstack/ev-rs

Length of output: 446


🏁 Script executed:

# Search for AccountId in all files
fd -e rs -x rg -l "struct AccountId" | head -5

Repository: evstack/ev-rs

Length of output: 247


🏁 Script executed:

# Try to find AccountId definition
rg -B2 -A10 "pub struct AccountId|impl AccountId" | head -80

Repository: evstack/ev-rs

Length of output: 4312


🏁 Script executed:

# Look at the actual file structure for AccountId
cat -n bin/testapp/src/lib.rs | head -50

Repository: evstack/ev-rs

Length of output: 2137


🏁 Script executed:

rg -A5 "fn from_u64" crates/app/sdk/core/src/lib.rs

Repository: evstack/ev-rs

Length of output: 303


🏁 Script executed:

# Let's see more of the AccountId implementation
cat -n crates/app/sdk/core/src/lib.rs | sed -n '1,100p'

Repository: evstack/ev-rs

Length of output: 3966


🏁 Script executed:

# Get more context around AccountId
cat -n crates/app/sdk/core/src/lib.rs | sed -n '1,80p'

Repository: evstack/ev-rs

Length of output: 3280


🏁 Script executed:

# Check if there are other references to minter_id or from_u64 in the codebase
rg -B3 -A3 "AccountId::from_u64|from_u64" | head -50

Repository: evstack/ev-rs

Length of output: 3159


🏁 Script executed:

# Look at the test data to understand what values are being used
cat -n bin/testapp/src/genesis_config.rs | sed -n '150,250p'

Repository: evstack/ev-rs

Length of output: 4136


🏁 Script executed:

# Let's check the actual byte layout difference between from_u64 and the manual encoding
python3 << 'EOF'
# Show what from_u64 does vs what the current code does for a test value
test_value = 100

# from_u64 encoding (places u64 in last 8 bytes)
from_u64_bytes = [0] * 24 + list(test_value.to_bytes(8, 'big'))
print(f"from_u64({test_value}) bytes: {[f'{b:02x}' for b in from_u64_bytes]}")

# Current code with u128 (places u128 in last 16 bytes)
current_bytes = [0] * 16 + list(test_value.to_bytes(16, 'big'))
print(f"Current u128 code bytes: {[f'{b:02x}' for b in current_bytes]}")

print(f"\nAre they the same for value {test_value}? {from_u64_bytes == current_bytes}")

# Test with a large value that wouldn't fit in u64
large_value = 2**65  # Larger than u64::MAX
try:
    large_u64_bytes = list(large_value.to_bytes(8, 'big'))
except:
    print(f"\nValue {large_value} cannot fit in u64")
    large_u128_bytes = [0] * 16 + list(large_value.to_bytes(16, 'big'))
    print(f"But it fits in u128 (current code)")
EOF

Repository: evstack/ev-rs

Length of output: 608


🏁 Script executed:

# Check if there are any usages that might pass large u128 values to minter_id
rg "minter_id\s*[:=]" -B2 -A2

Repository: evstack/ev-rs

Length of output: 699


🏁 Script executed:

# Check if minter_id ever gets assigned values larger than u64::MAX in practice
rg "minter_id\s*[:=]\s*[0-9]" -B2 -A2

Repository: evstack/ev-rs

Length of output: 403


🏁 Script executed:

# Look at what values are actually being used in tests and configuration
rg "minter_id.*100" bin/testapp/src/main.rs -B5 -A5

Repository: evstack/ev-rs

Length of output: 481


Normalize minter_id via AccountId::from_u64 with explicit bounds checking.

The field minter_id is declared as u128, but the code should only accept u64 values to maintain consistency with the canonical AccountId constructor pattern used elsewhere (e.g., const MINTER: AccountId = AccountId::from_u64(100002) in bin/testapp/src/lib.rs). The current manual byte encoding at lines 660–661 silently accepts values exceeding u64::MAX without validation.

Suggested fix
-    let minter = {
-        let mut b = [0u8; 32];
-        b[16..32].copy_from_slice(&genesis_config.minter_id.to_be_bytes());
-        AccountId::from_bytes(b)
-    };
+    let minter = AccountId::from_u64(
+        u64::try_from(genesis_config.minter_id)
+            .expect("genesis minter_id must fit in u64"),
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let minter = {
let mut b = [0u8; 32];
b[16..32].copy_from_slice(&genesis_config.minter_id.to_be_bytes());
AccountId::from_bytes(b)
};
let minter = AccountId::from_u64(
u64::try_from(genesis_config.minter_id)
.expect("genesis minter_id must fit in u64"),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/evd/src/main.rs` around lines 658 - 662, The code builds `minter` by
manual byte-copy from `genesis_config.minter_id` which is u128 and can silently
overflow the intended u64 account space; replace that logic by validating
`genesis_config.minter_id` fits in u64 and then construct the AccountId via
`AccountId::from_u64(...)` (e.g., check `minter_id <= u64::MAX` and cast to u64
or return/panic on out-of-range), removing the manual
`b[16..32].copy_from_slice(...)`/`AccountId::from_bytes` approach so the value
is normalized and bounded consistently with other usages like `const MINTER:
AccountId = AccountId::from_u64(100002)`.

- Change minter_id from u128 to u64 in EvdGenesisConfig
- Replace manual byte-packing with AccountId::from_u64 in evd and testapp
- Fix serde round-trip for SenderSpec/RecipientSpec hex AccountId variants
- Change MockEnvironment::new to accept u64 and use from_u64
- Extract INITIAL_ACCOUNT_NUMBER constant in runtime_api_impl

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tac0turtle tac0turtle merged commit 05639d7 into main Feb 28, 2026
5 of 6 checks passed
@tac0turtle tac0turtle deleted the cleanup/account-id-numeric branch February 28, 2026 10:32
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