Skip to content

Commit

Permalink
[xp] refactor address class
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah committed Apr 24, 2024
1 parent 32820a1 commit d64c835
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 56 deletions.
88 changes: 40 additions & 48 deletions packages/massa-web3/src/experimental/basicElements/address.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Base58 from '../crypto/base58'
import Serializer from '../crypto/interfaces/serializer'
import { Version } from '../crypto/interfaces/versioner'
import { checkPrefix } from './internal'
import { PublicKey } from './keys'
import varint from 'varint'

const ADDRESS_PREFIX = 'A'
const ADDRESS_USER_PREFIX = 'U'
const ADDRESS_CONTRACT_PREFIX = 'S'
const ADDRESS_USER_PREFIX = 'AU'
const ADDRESS_CONTRACT_PREFIX = 'AS'
const UNDERLYING_HASH_LEN = 32

const DEFAULT_VERSION = Version.V0

/**
* A class representing an address.
*
Expand All @@ -17,37 +18,28 @@ const UNDERLYING_HASH_LEN = 32
*
* @privateRemarks
* Interfaces are used to make the code more modular. To add a new version, you simply need to:
* - extend the `initFromVersion` method:
* - Add a new case in the switch statement with the new algorithms to use.
* - Add a new default version matching the last version.
* - Change the DEFAULT_VERSION version matching the last version.
* - check the `fromPublicKey` method to potentially adapt how an address is derived from a public key.
* - Voila! The code will automatically handle the new version.
*/
export class Address {
private bytes: Uint8Array

protected constructor(
public serializer: Serializer,
public version: Version,
public isEOA: boolean
) {}
public version = DEFAULT_VERSION
public isEOA = false

/**
* Initialize a new address object from a version.
*
* @param version - The version of the address.
*
* @returns A new address instance.
*
* @throws If the version is not supported.
*/
protected static initFromVersion(version: Version = Version.V0): Address {
switch (version) {
case Version.V0:
return new Address(new Base58(), version, false)
default:
throw new Error(`unsupported version: ${version}`)
protected constructor(public serializer: Serializer = new Base58()) {}

private getPrefix(str: string): string {
const expected = [ADDRESS_USER_PREFIX, ADDRESS_CONTRACT_PREFIX]
for (let prefix of expected) {
if (str.startsWith(prefix)) {
return prefix
}
}
throw new Error(
`invalid address prefix: one of ${expected.join(' or ')} was expected.`
)
}

/**
Expand All @@ -60,29 +52,24 @@ export class Address {
* @throws If the address string is invalid.
*/
public static fromString(str: string): Address {
const address = Address.initFromVersion()
const address = new Address()

try {
const prefix = checkPrefix(
str,
ADDRESS_PREFIX + ADDRESS_USER_PREFIX,
ADDRESS_PREFIX + ADDRESS_CONTRACT_PREFIX
)
const prefix = address.getPrefix(str)

address.isEOA = str[ADDRESS_PREFIX.length] === ADDRESS_USER_PREFIX
const versionedHash = address.serializer.deserialize(
address.isEOA = prefix === ADDRESS_USER_PREFIX
const versionedBytes = address.serializer.deserialize(
str.slice(prefix.length)
)

address.bytes = Uint8Array.from([
...varint.encode(address.isEOA ? 0 : 1),
...versionedHash,
...versionedBytes,
])
address.version = address.getVersion()
} catch (e) {
throw new Error(`invalid address string: ${e.message}`)
}

return address
}

Expand Down Expand Up @@ -118,13 +105,17 @@ export class Address {
*
* @returns A new address object.
*/
public static fromPublicKey(publicKey: PublicKey): Address {
const address = Address.initFromVersion()
const hash = publicKey.hasher.hash(publicKey.toBytes())
public static fromPublicKey(
publicKey: PublicKey,
version = DEFAULT_VERSION
): Address {
const address = new Address()
const rawBytes = publicKey.hasher.hash(publicKey.toBytes())
address.version = version
address.bytes = Uint8Array.from([
0 /* EOA*/,
...varint.encode(address.version),
...hash,
...varint.encode(version),
...rawBytes,
])
address.isEOA = true
return address
Expand All @@ -138,7 +129,7 @@ export class Address {
* @returns A new address object.
*/
public static fromBytes(bytes: Uint8Array): Address {
const address = Address.initFromVersion()
const address = new Address()
address.bytes = bytes
address.isEOA = address.getType() === 0
address.version = address.getVersion()
Expand Down Expand Up @@ -168,17 +159,18 @@ export class Address {
// decode version
varint.decode(this.bytes)
const versionedBytes = this.bytes.slice(varint.decode.bytes)
return `${ADDRESS_PREFIX}${
return `${
this.isEOA ? ADDRESS_USER_PREFIX : ADDRESS_CONTRACT_PREFIX
}${this.serializer.serialize(versionedBytes)}`
}

/**
* Get byte length of address in binary format .
* Get address in binary format from a bytes buffer.
* The address should be the first element in the buffer.
*
* @returns The address length in bytes.
* @returns The address in bytes format.
*/
static getByteLength(data: Uint8Array): number {
static extractFromBuffer(data: Uint8Array): Uint8Array {
// addr type
varint.decode(data)
let addrByteLen = varint.decode.bytes
Expand All @@ -187,6 +179,6 @@ export class Address {
addrByteLen += varint.decode.bytes

addrByteLen += UNDERLYING_HASH_LEN
return addrByteLen
return data.slice(0, addrByteLen)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,9 @@ export class OperationManager {

switch (operationDetails.type) {
case OperationType.Transaction: {
const addrLen = Address.getByteLength(data.slice(offset))
const recipientAddress = Address.fromBytes(
data.slice(offset, offset + addrLen)
)
offset += addrLen
const addrBytes = Address.extractFromBuffer(data.slice(offset))

Check warning on line 167 in packages/massa-web3/src/experimental/basicElements/operationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
const recipientAddress = Address.fromBytes(addrBytes)

Check warning on line 168 in packages/massa-web3/src/experimental/basicElements/operationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
offset += addrBytes.length

Check warning on line 169 in packages/massa-web3/src/experimental/basicElements/operationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
return {
...operationDetails,
recipientAddress,
Expand Down
6 changes: 3 additions & 3 deletions packages/massa-web3/test/experimental/unit/address.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ describe('Address tests', () => {

test('getByteLength returns correct length', () => {
const address = Address.fromPublicKey(account.publicKey)
const bytes = address.toBytes()
const byteLength = Address.getByteLength(bytes)
const buffer = Uint8Array.from([...address.toBytes(), 1, 2, 3, 4])
const addressBytes = Address.extractFromBuffer(buffer)

expect(byteLength).toBe(bytes.length)
expect(addressBytes).toStrictEqual(address.toBytes())
})
})

0 comments on commit d64c835

Please sign in to comment.