Skip to content

Commit 05639d7

Browse files
tac0turtleclaude
andauthored
Cleanup/account id numeric (#19)
* swarm: migrate core SDK AccountId::new() to from_bytes() literals 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> * swarm: migrate AccountId::new() to from_bytes() in RPC, testing infra, 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> * swarm: migrate AccountId::new() to from_bytes() in SDK extensions and 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> * cleanup: remove AccountId::new/inner numeric compat methods 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> * refactor: replace AccountId::from_bytes literals with from_u64 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> * refactor: use AccountId in SenderSpec/RecipientSpec instead of [u8; 32] 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> * cleanup: address PR review — eliminate remaining manual byte-packing - 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> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 113efeb commit 05639d7

34 files changed

Lines changed: 327 additions & 298 deletions

File tree

bin/evd/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,7 @@ fn run_custom_genesis<S: ReadonlyKV + Storage>(
655655
(addr.into_array(), acc.balance)
656656
})
657657
.collect();
658-
let minter = AccountId::new(genesis_config.minter_id);
658+
let minter = AccountId::from_u64(genesis_config.minter_id);
659659
let metadata = genesis_config.token.to_metadata();
660660

661661
let gas_config = default_gas_config();

bin/testapp/src/genesis_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub type FundedAccount = ([u8; 20], u128);
1212
#[derive(Deserialize)]
1313
pub struct EvdGenesisConfig {
1414
pub token: TokenConfig,
15-
pub minter_id: u128,
15+
pub minter_id: u64,
1616
pub accounts: Vec<AccountConfig>,
1717
}
1818

bin/testapp/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use evolve_token::account::{Token, TokenRef};
1717
use evolve_tx_eth::TxContext;
1818
use evolve_tx_eth::{register_runtime_contract_account, resolve_or_create_eoa_account};
1919

20-
pub const MINTER: AccountId = AccountId::new(100_002);
20+
pub const MINTER: AccountId = AccountId::from_u64(100002);
2121

2222
pub struct MempoolNoOpPostTx;
2323

@@ -42,7 +42,7 @@ pub type MempoolStf = Stf<
4242
MempoolNoOpPostTx,
4343
>;
4444

45-
pub const PLACEHOLDER_ACCOUNT: AccountId = AccountId::new(u128::MAX);
45+
pub const PLACEHOLDER_ACCOUNT: AccountId = AccountId::from_u64(u64::MAX);
4646

4747
/// Default gas configuration for the test application.
4848
pub fn default_gas_config() -> StorageGasConfig {

bin/testapp/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ fn run_custom_genesis<S: ReadonlyKV + Storage>(
218218
return Err("custom genesis requires at least one account".into());
219219
}
220220

221-
let minter = AccountId::new(config.minter_id);
221+
let minter = AccountId::from_u64(config.minter_id);
222222
let metadata = config.token.to_metadata();
223223

224224
let genesis_block = BlockContext::new(0, 0);

bin/testapp/tests/mempool_e2e.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ fn setup_genesis(
270270

271271
let init_storage = AsyncMockStorage::new();
272272
let gas_config = default_gas_config();
273-
let stf = build_mempool_stf(gas_config.clone(), AccountId::new(0));
273+
let stf = build_mempool_stf(gas_config.clone(), AccountId::from_u64(0));
274274

275275
let (genesis_state, genesis_accounts) = do_eth_genesis(
276276
&stf,

bin/txload/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ mod tests {
567567

568568
#[test]
569569
fn build_transfer_calldata_has_selector_and_borsh_args() {
570-
let recipient = AccountId::new(42);
570+
let recipient = AccountId::from_u64(42);
571571
let amount = 999_u128;
572572

573573
let calldata = build_transfer_calldata(recipient, amount).unwrap();

crates/app/genesis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ evolve_stf = { workspace = true }
1313
evolve_stf_traits = { workspace = true }
1414

1515
borsh = { workspace = true }
16+
hex = "0.4"
1617
serde = { version = "1.0", features = ["derive"] }
1718
serde_json = "1.0"
1819
thiserror = "1.0"

crates/app/genesis/src/file.rs

Lines changed: 116 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub struct GenesisTxJson {
4040
pub id: Option<String>,
4141

4242
/// Sender specification.
43-
/// - "system": Use the system account (AccountId::new(0))
43+
/// - "system": Use the system account (AccountId::from_u64(0))
4444
/// - "$ref": Reference a previously created account
4545
/// - number: Use a specific account ID
4646
#[serde(default = "default_sender")]
@@ -64,15 +64,25 @@ fn default_sender() -> SenderSpec {
6464
}
6565

6666
/// Specification for the sender of a genesis transaction.
67-
#[derive(Debug, Clone, Default, Serialize)]
67+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
6868
pub enum SenderSpec {
6969
/// Use the system account
7070
#[default]
7171
System,
7272
/// Reference a previously created account (starts with $)
7373
Reference(String),
7474
/// Use a specific account ID
75-
AccountId(u128),
75+
AccountId(AccountId),
76+
}
77+
78+
impl Serialize for SenderSpec {
79+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80+
match self {
81+
SenderSpec::System => serializer.serialize_str("system"),
82+
SenderSpec::Reference(r) => serializer.serialize_str(r),
83+
SenderSpec::AccountId(id) => serializer.serialize_str(&hex::encode(id.to_bytes())),
84+
}
85+
}
7686
}
7787

7888
impl<'de> serde::Deserialize<'de> for SenderSpec {
@@ -85,26 +95,47 @@ impl<'de> serde::Deserialize<'de> for SenderSpec {
8595
let value = serde_json::Value::deserialize(deserializer)?;
8696
match value {
8797
serde_json::Value::String(s) if s == "system" => Ok(SenderSpec::System),
88-
serde_json::Value::String(s) => Ok(SenderSpec::Reference(s)),
89-
serde_json::Value::Number(n) => n
90-
.as_u64()
91-
.map(|v| SenderSpec::AccountId(v as u128))
92-
.or_else(|| n.as_i64().map(|v| SenderSpec::AccountId(v as u128)))
93-
.ok_or_else(|| D::Error::custom("invalid account ID number")),
98+
serde_json::Value::String(s) if s.starts_with('$') => Ok(SenderSpec::Reference(s)),
99+
serde_json::Value::String(s) => {
100+
// Try hex-encoded account ID, fall back to reference
101+
match hex::decode(s.trim_start_matches("0x")) {
102+
Ok(b) if b.len() == 32 => {
103+
let bytes: [u8; 32] = b.try_into().unwrap();
104+
Ok(SenderSpec::AccountId(AccountId::from_bytes(bytes)))
105+
}
106+
_ => Ok(SenderSpec::Reference(s)),
107+
}
108+
}
109+
serde_json::Value::Number(n) => {
110+
let v = n
111+
.as_u64()
112+
.ok_or_else(|| D::Error::custom("invalid account ID number"))?;
113+
Ok(SenderSpec::AccountId(AccountId::from_u64(v)))
114+
}
94115
_ => Err(D::Error::custom("expected string or number for sender")),
95116
}
96117
}
97118
}
98119

99120
/// Specification for the recipient of a genesis transaction.
100-
#[derive(Debug, Clone, Serialize)]
121+
#[derive(Debug, Clone, PartialEq, Eq)]
101122
pub enum RecipientSpec {
102123
/// Use the runtime account for account creation
103124
Runtime,
104125
/// Reference a previously created account (starts with $)
105126
Reference(String),
106127
/// Use a specific account ID
107-
AccountId(u128),
128+
AccountId(AccountId),
129+
}
130+
131+
impl Serialize for RecipientSpec {
132+
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
133+
match self {
134+
RecipientSpec::Runtime => serializer.serialize_str("runtime"),
135+
RecipientSpec::Reference(r) => serializer.serialize_str(r),
136+
RecipientSpec::AccountId(id) => serializer.serialize_str(&hex::encode(id.to_bytes())),
137+
}
138+
}
108139
}
109140

110141
impl<'de> serde::Deserialize<'de> for RecipientSpec {
@@ -117,12 +148,23 @@ impl<'de> serde::Deserialize<'de> for RecipientSpec {
117148
let value = serde_json::Value::deserialize(deserializer)?;
118149
match value {
119150
serde_json::Value::String(s) if s == "runtime" => Ok(RecipientSpec::Runtime),
120-
serde_json::Value::String(s) => Ok(RecipientSpec::Reference(s)),
121-
serde_json::Value::Number(n) => n
122-
.as_u64()
123-
.map(|v| RecipientSpec::AccountId(v as u128))
124-
.or_else(|| n.as_i64().map(|v| RecipientSpec::AccountId(v as u128)))
125-
.ok_or_else(|| D::Error::custom("invalid account ID number")),
151+
serde_json::Value::String(s) if s.starts_with('$') => Ok(RecipientSpec::Reference(s)),
152+
serde_json::Value::String(s) => {
153+
// Try hex-encoded account ID, fall back to reference
154+
match hex::decode(s.trim_start_matches("0x")) {
155+
Ok(b) if b.len() == 32 => {
156+
let bytes: [u8; 32] = b.try_into().unwrap();
157+
Ok(RecipientSpec::AccountId(AccountId::from_bytes(bytes)))
158+
}
159+
_ => Ok(RecipientSpec::Reference(s)),
160+
}
161+
}
162+
serde_json::Value::Number(n) => {
163+
let v = n
164+
.as_u64()
165+
.ok_or_else(|| D::Error::custom("invalid account ID number"))?;
166+
Ok(RecipientSpec::AccountId(AccountId::from_u64(v)))
167+
}
126168
_ => Err(D::Error::custom("expected string or number for recipient")),
127169
}
128170
}
@@ -189,27 +231,31 @@ impl GenesisFile {
189231

190232
let mut txs = Vec::with_capacity(self.transactions.len());
191233
let mut id_to_account: HashMap<String, AccountId> = HashMap::new();
192-
let mut next_account_id: u128 = 1; // Start from 1, 0 is system
234+
// Start from 1, 0 is system
235+
let mut next_account_id = AccountId::from_u64(1);
193236

194237
for (idx, tx_json) in self.transactions.iter().enumerate() {
195238
// Resolve sender
196-
let sender =
197-
match &tx_json.sender {
198-
SenderSpec::System => SYSTEM_ACCOUNT_ID,
199-
SenderSpec::Reference(r) if r.starts_with('$') => {
200-
let ref_id = &r[1..];
201-
*id_to_account.get(ref_id).ok_or_else(|| {
202-
GenesisError::InvalidReference(ref_id.to_string(), idx)
203-
})?
204-
}
205-
SenderSpec::Reference(r) => {
206-
// Treat as account ID string
207-
AccountId::new(r.parse().map_err(|_| {
239+
let sender = match &tx_json.sender {
240+
SenderSpec::System => SYSTEM_ACCOUNT_ID,
241+
SenderSpec::Reference(r) if r.starts_with('$') => {
242+
let ref_id = &r[1..];
243+
*id_to_account
244+
.get(ref_id)
245+
.ok_or_else(|| GenesisError::InvalidReference(ref_id.to_string(), idx))?
246+
}
247+
SenderSpec::Reference(r) => {
248+
// Treat as hex-encoded 32-byte account ID
249+
let bytes = hex::decode(r.trim_start_matches("0x"))
250+
.ok()
251+
.and_then(|b| <[u8; 32]>::try_from(b).ok())
252+
.ok_or_else(|| {
208253
GenesisError::ParseError(format!("invalid sender: {}", r))
209-
})?)
210-
}
211-
SenderSpec::AccountId(id) => AccountId::new(*id),
212-
};
254+
})?;
255+
AccountId::from_bytes(bytes)
256+
}
257+
SenderSpec::AccountId(id) => *id,
258+
};
213259

214260
// Resolve recipient
215261
let recipient = match &tx_json.recipient {
@@ -221,12 +267,16 @@ impl GenesisFile {
221267
.ok_or_else(|| GenesisError::InvalidReference(ref_id.to_string(), idx))?
222268
}
223269
RecipientSpec::Reference(r) => {
224-
// Treat as account ID string
225-
AccountId::new(r.parse().map_err(|_| {
226-
GenesisError::ParseError(format!("invalid recipient: {}", r))
227-
})?)
270+
// Treat as hex-encoded 32-byte account ID
271+
let bytes = hex::decode(r.trim_start_matches("0x"))
272+
.ok()
273+
.and_then(|b| <[u8; 32]>::try_from(b).ok())
274+
.ok_or_else(|| {
275+
GenesisError::ParseError(format!("invalid recipient: {}", r))
276+
})?;
277+
AccountId::from_bytes(bytes)
228278
}
229-
RecipientSpec::AccountId(id) => AccountId::new(*id),
279+
RecipientSpec::AccountId(id) => *id,
230280
};
231281

232282
// Resolve $references in the message payload
@@ -244,9 +294,8 @@ impl GenesisFile {
244294
// If this transaction has an ID, assign it the next account ID
245295
// This assumes the transaction creates an account
246296
if let Some(id) = &tx_json.id {
247-
let account_id = AccountId::new(next_account_id);
248-
id_to_account.insert(id.clone(), account_id);
249-
next_account_id += 1;
297+
id_to_account.insert(id.clone(), next_account_id);
298+
next_account_id = next_account_id.increase();
250299
}
251300

252301
txs.push(genesis_tx);
@@ -267,8 +316,9 @@ fn resolve_references_in_value(
267316
let account_id = id_to_account
268317
.get(ref_id)
269318
.ok_or_else(|| GenesisError::InvalidReference(ref_id.to_string(), 0))?;
270-
// Use string representation for u128 since JSON numbers are limited to i64/u64
271-
Ok(serde_json::Value::String(account_id.inner().to_string()))
319+
Ok(serde_json::Value::String(hex::encode(
320+
account_id.as_bytes(),
321+
)))
272322
}
273323
serde_json::Value::Array(arr) => {
274324
let resolved: Result<Vec<_>, _> = arr
@@ -360,7 +410,7 @@ mod tests {
360410
#[test]
361411
fn test_resolve_references_in_value() {
362412
let mut id_to_account = HashMap::new();
363-
id_to_account.insert("alice".to_string(), AccountId::new(1));
413+
id_to_account.insert("alice".to_string(), AccountId::from_u64(1));
364414

365415
let value = serde_json::json!({
366416
"account": "$alice",
@@ -372,10 +422,19 @@ mod tests {
372422

373423
let resolved = resolve_references_in_value(&value, &id_to_account).unwrap();
374424

375-
// Account IDs are resolved as strings (for u128 compatibility)
376-
assert_eq!(resolved["account"], "1");
377-
assert_eq!(resolved["nested"]["ref"], "1");
378-
assert_eq!(resolved["list"][0], "1");
425+
// Account IDs are resolved as hex strings
426+
assert_eq!(
427+
resolved["account"],
428+
"0000000000000000000000000000000000000000000000000000000000000001"
429+
);
430+
assert_eq!(
431+
resolved["nested"]["ref"],
432+
"0000000000000000000000000000000000000000000000000000000000000001"
433+
);
434+
assert_eq!(
435+
resolved["list"][0],
436+
"0000000000000000000000000000000000000000000000000000000000000001"
437+
);
379438
assert_eq!(resolved["list"][1], "literal");
380439
}
381440

@@ -459,10 +518,10 @@ mod tests {
459518
]
460519
}"#;
461520
let genesis = GenesisFile::parse(json).unwrap();
462-
assert!(matches!(
521+
assert_eq!(
463522
genesis.transactions[0].sender,
464-
SenderSpec::AccountId(123)
465-
));
523+
SenderSpec::AccountId(AccountId::from_u64(123))
524+
);
466525
}
467526

468527
#[test]
@@ -488,10 +547,10 @@ mod tests {
488547
]
489548
}"#;
490549
let genesis = GenesisFile::parse(json).unwrap();
491-
assert!(matches!(
550+
assert_eq!(
492551
genesis.transactions[0].recipient,
493-
RecipientSpec::AccountId(456)
494-
));
552+
RecipientSpec::AccountId(AccountId::from_u64(456))
553+
);
495554
}
496555

497556
#[test]
@@ -577,8 +636,8 @@ mod tests {
577636
let txs = genesis.to_transactions(&registry).unwrap();
578637

579638
assert_eq!(txs.len(), 3);
580-
assert_eq!(txs[2].sender, AccountId::new(1));
581-
assert_eq!(txs[2].recipient, AccountId::new(1));
639+
assert_eq!(txs[2].sender, AccountId::from_u64(1));
640+
assert_eq!(txs[2].recipient, AccountId::from_u64(1));
582641
}
583642

584643
#[test]

0 commit comments

Comments
 (0)