Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 41 additions & 77 deletions packages/util/src/account.ts
Copy link
Member

Choose a reason for hiding this comment

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

I see changes to the account file (which is good!), could you directly deprecate the constructor of Account and document that createAccount should be used? Context: #3958. I think it's fine to also do that in this PR and not create a new one, since it's only docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { assertIsBytes, assertIsHexString, assertIsString } from './helpers.ts'
import { stripHexPrefix } from './internal.ts'

import type { BigIntLike, BytesLike, PrefixedHexString } from './types.ts'
import type { BigIntLike, BytesLike, NestedUint8Array, PrefixedHexString } from './types.ts'

export interface AccountData {
nonce?: BigIntLike
Expand All @@ -39,6 +39,35 @@

export type AccountBodyBytes = [Uint8Array, Uint8Array, Uint8Array, Uint8Array]

/**
* Handles the null indicator for RLP encoded accounts
* @returns {null} is the null indicator is 0
* @returns The unchanged value is the null indicator is 1
* @throws if the null indicator is > 1
* @throws if the length of values is < 2
* @param value The value to convert
* @returns The converted value
*/
function handleNullIndicator(values: NestedUint8Array | Uint8Array): Uint8Array | null {
// Needed if some values are not provided to the array (e.g. partial account RLP)
if (values[0] === undefined) {
return null
}

const nullIndicator = bytesToInt(values[0] as Uint8Array)

if (nullIndicator === 0) {
return null
}
if (nullIndicator > 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${nullIndicator}`)
}
if (values.length < 2) {
throw EthereumJSErrorWithoutCode(`Invalid values length=${values.length}`)

Check warning on line 66 in packages/util/src/account.ts

View check run for this annotation

Codecov / codecov/patch

packages/util/src/account.ts#L66

Added line #L66 was not covered by tests
}
return values[1] as Uint8Array
}

/**
* Account class to load and maintain the basic account objects.
* Supports partial loading and access required for verkle with null
Expand Down Expand Up @@ -325,91 +354,26 @@
}

export function createPartialAccountFromRLP(serialized: Uint8Array) {
const values = RLP.decode(serialized) as Uint8Array[][]
const values = RLP.decode(serialized)

if (!Array.isArray(values)) {
throw EthereumJSErrorWithoutCode('Invalid serialized account input. Must be array')
}

let nonce = null
if (!Array.isArray(values[0])) {
throw EthereumJSErrorWithoutCode('Invalid partial nonce encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[0][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for nonce`)
}
if (isNotNullIndicator === 1) {
nonce = bytesToBigInt(values[0][1])
for (const value of values) {
// Ensure that each array item is an array
if (!Array.isArray(value)) {
throw EthereumJSErrorWithoutCode('Invalid partial encoding. Each item must be an array')
}
}

let balance = null
if (!Array.isArray(values[1])) {
throw EthereumJSErrorWithoutCode('Invalid partial balance encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[1][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for balance`)
}
if (isNotNullIndicator === 1) {
balance = bytesToBigInt(values[1][1])
}
}
const [nonceRaw, balanceRaw, storageRoot, codeHash, codeSizeRaw, versionRaw] =
values.map(handleNullIndicator)

let storageRoot = null
if (!Array.isArray(values[2])) {
throw EthereumJSErrorWithoutCode('Invalid partial storageRoot encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[2][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(
`Invalid isNullIndicator=${isNotNullIndicator} for storageRoot`,
)
}
if (isNotNullIndicator === 1) {
storageRoot = values[2][1]
}
}

let codeHash = null
if (!Array.isArray(values[3])) {
throw EthereumJSErrorWithoutCode('Invalid partial codeHash encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[3][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeHash`)
}
if (isNotNullIndicator === 1) {
codeHash = values[3][1]
}
}

let codeSize = null
if (!Array.isArray(values[4])) {
throw EthereumJSErrorWithoutCode('Invalid partial codeSize encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[4][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for codeSize`)
}
if (isNotNullIndicator === 1) {
codeSize = bytesToInt(values[4][1])
}
}

let version = null
if (!Array.isArray(values[5])) {
throw EthereumJSErrorWithoutCode('Invalid partial version encoding. Must be array')
} else {
const isNotNullIndicator = bytesToInt(values[5][0])
if (isNotNullIndicator !== 0 && isNotNullIndicator !== 1) {
throw EthereumJSErrorWithoutCode(`Invalid isNullIndicator=${isNotNullIndicator} for version`)
}
if (isNotNullIndicator === 1) {
version = bytesToInt(values[5][1])
}
}
const nonce = nonceRaw === null ? null : bytesToBigInt(nonceRaw)
const balance = balanceRaw === null ? null : bytesToBigInt(balanceRaw)
const codeSize = codeSizeRaw === null ? null : bytesToInt(codeSizeRaw)
const version = versionRaw === null ? null : bytesToInt(versionRaw)

return createPartialAccount({ balance, nonce, storageRoot, codeHash, codeSize, version })
}
Expand Down
9 changes: 7 additions & 2 deletions packages/util/src/bytes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ export const unprefixedHexToBytes = (hex: string): Uint8Array => {
return nobleH2B(padToEven(hex))
}

/**
* Converts a {@link Uint8Array} to a {@link PrefixedHexString}
* @param {Uint8Array} bytes the bytes to convert
* @returns {PrefixedHexString} the hex string
* @dev Returns `0x` if provided an empty Uint8Array
*/
export const bytesToHex = (bytes: Uint8Array): PrefixedHexString => {
if (bytes === undefined || bytes.length === 0) return '0x'
const unprefixedHex = bytesToUnprefixedHex(bytes)
return `0x${unprefixedHex}`
}
Expand Down Expand Up @@ -92,7 +97,7 @@ export const intToHex = (i: number): PrefixedHexString => {
if (!Number.isSafeInteger(i) || i < 0) {
throw EthereumJSErrorWithoutCode(`Received an invalid integer type: ${i}`)
}
return ('0x' + i.toString(16)) as PrefixedHexString
return `0x${i.toString(16)}`
}

/**
Expand Down
24 changes: 12 additions & 12 deletions packages/util/test/account.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -999,84 +999,84 @@ describe('createPartialAccountFromRLP', () => {
data: [KECCAK256_RLP, [], [], [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial nonce encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid partial balance encoding',
data: [[], KECCAK256_RLP, [], [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial balance encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid partial storageRoot encoding',
data: [[], [], KECCAK256_RLP, [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial storageRoot encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid partial codeHash encoding',
data: [[], [], [], KECCAK256_RLP, [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial codeHash encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid partial codeSize encoding',
data: [[], [], [], [], KECCAK256_RLP, []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial codeSize encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid partial version encoding',
data: [[], [], [], [], [], KECCAK256_RLP],
shouldThrow: true,
expected: null,
errorRegex: /Invalid partial version encoding/,
errorRegex: /Invalid partial encoding. Each item must be an array/,
},
{
description: 'should throw: invalid isNullIndicator=2 for nonce',
data: [[toBytes(2)], [], [], [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for nonce/,
errorRegex: /Invalid isNullIndicator=2/,
},
{
description: 'should throw: invalid isNullIndicator=2 for balance',
data: [[], [toBytes(2)], [], [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for balance/,
errorRegex: /Invalid isNullIndicator=2/,
},
{
description: 'should throw: invalid isNullIndicator=2 for storageRoot',
data: [[], [], [toBytes(2)], [], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for storageRoot/,
errorRegex: /Invalid isNullIndicator=2/,
},
{
description: 'should throw: invalid isNullIndicator=2 for codeHash',
data: [[], [], [], [toBytes(2)], [], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for codeHash/,
errorRegex: /Invalid isNullIndicator=2/,
},
{
description: 'should throw: invalid isNullIndicator=2 for codeSize',
data: [[], [], [], [], [toBytes(2)], []],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for codeSize/,
errorRegex: /Invalid isNullIndicator=2/,
},
{
description: 'should throw: invalid isNullIndicator=2 for version',
data: [[], [], [], [], [], [toBytes(2)]],
shouldThrow: true,
expected: null,
errorRegex: /Invalid isNullIndicator=2 for version/,
errorRegex: /Invalid isNullIndicator=2/,
},
]

Expand Down
Loading