Skip to content
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

Blob #71

Merged
merged 10 commits into from
Jul 28, 2023
9 changes: 2 additions & 7 deletions src/api/v1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AnyAction,
Asset,
Authority,
Blob,
BlockId,
BlockTimestamp,
Bytes,
Expand Down Expand Up @@ -124,13 +125,7 @@ export class GetRawAbiResponse extends Struct {
@Struct.field('name') declare account_name: Name
@Struct.field('checksum256') declare code_hash: Checksum256
@Struct.field('checksum256') declare abi_hash: Checksum256
@Struct.field('string') declare abi: ABI
static from(data: any) {
return super.from({
...data,
abi: Serializer.decode({data: Bytes.from(Buffer.from(data.abi, 'base64')), type: ABI}),
})
}
@Struct.field(Blob) declare abi: Blob
}

@Struct.type('account_object')
Expand Down
12 changes: 9 additions & 3 deletions src/chain/abi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {isInstanceOf} from '../utils'
import type {ABISerializableObject} from '../serializer/serializable'
import type {ABIDecoder} from '../serializer/decoder'
import {abiDecode, ABIDecoder} from '../serializer/decoder'
import {abiEncode, ABIEncoder} from '../serializer/encoder'

import {Name, NameType} from '../'
import {Blob, Name, NameType} from '../'

export type ABIDef = string | Partial<ABI.Def> | ABI
export type ABIDef = string | Partial<ABI.Def> | ABI | Blob

export class ABI implements ABISerializableObject {
static abiName = 'abi'
Expand Down Expand Up @@ -39,6 +39,12 @@ export class ABI implements ABISerializableObject {
if (isInstanceOf(value, ABI)) {
return value
}
if (isInstanceOf(value, Blob)) {
return abiDecode({
data: value.array,
type: this,
})
}
if (typeof value === 'string') {
return new ABI(JSON.parse(value))
}
Expand Down
88 changes: 88 additions & 0 deletions src/chain/blob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {ABISerializableObject} from '../serializer/serializable'
import {ABIEncoder} from '../serializer/encoder'
import {arrayEquals, isInstanceOf} from '../utils'

export type BlobType = Blob | string

export class Blob implements ABISerializableObject {
static abiName = 'blob'

/**
* Create a new Bytes instance.
* @note Make sure to take a [[copy]] before mutating the bytes as the underlying source is not copied here.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Docstring says Bytes and references non existent method

Copy link
Member Author

Choose a reason for hiding this comment

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

Bytes is where I copied the class structure from instead of writing it from scratch... my sloppy copy/pasta. Whoops 😅

*/
static from(value: BlobType): Blob {
if (isInstanceOf(value, this)) {
return value
}
if (typeof value === 'string') {
return this.fromString(value)
}
throw new Error('Invalid blob')
}

static fromString(value: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to handle the invalid base64 padding nodeos outputs

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I have tests ensuring the invalid stuff works here: 1cd6b9b

// If buffer is available, use it (maintains support for nodejs 14)
if (typeof Buffer === 'function') {
return new this(new Uint8Array(Buffer.from(value, 'base64')))
}
// fix up base64 padding from nodeos
switch (value.length % 4) {
case 2:
value += '=='
break
case 3:
value += '='
break
case 1:
value = value.substring(0, value.length - 1)
break
}
const string = atob(value)
const array = new Uint8Array(string.length)
for (let i = 0; i < string.length; i++) {
array[i] = string.charCodeAt(i)
}
return new this(array)
}

readonly array: Uint8Array

constructor(array: Uint8Array) {
this.array = array
}

equals(other: BlobType): boolean {
const self = this.constructor as typeof Blob
try {
return arrayEquals(this.array, self.from(other).array)
} catch {
return false
}
}

get base64String(): string {
// If buffer is available, use it (maintains support for nodejs 14)
if (typeof Buffer === 'function') {
return Buffer.from(this.array).toString('base64')
}
return btoa(this.utf8String)
}

/** UTF-8 string representation of this instance. */
get utf8String(): string {
return new TextDecoder().decode(this.array)
}

toABI(encoder: ABIEncoder) {
encoder.writeArray(this.array)
}

toString() {
return this.base64String
}

toJSON() {
return this.toString()
}
}
1 change: 1 addition & 0 deletions src/chain/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// types with no inter-dependencies
export * from './blob'
export * from './bytes'
export * from './checksum'
export * from './key-type'
Expand Down
7 changes: 5 additions & 2 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
APIClient,
APIError,
Asset,
Blob,
BlockId,
Bytes,
Checksum256,
Expand Down Expand Up @@ -82,8 +83,10 @@ suite('api v1', function () {
'd84356074da34a976528321472d73ac919227b9b01d9de59d8ade6d96440455c'
)
)
assert.instanceOf(response.abi, ABI)
assert.equal(response.abi.version, 'eosio::abi/1.2')
assert.instanceOf(response.abi, Blob)
const abi = ABI.from(response.abi)
assert.instanceOf(abi, ABI)
assert.equal(abi.version, 'eosio::abi/1.2')
})

test('chain get_account', async function () {
Expand Down
24 changes: 23 additions & 1 deletion test/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AnyTransaction,
Asset,
Authority,
Blob,
BlockId,
BlockTimestamp,
Bytes,
Expand All @@ -15,7 +16,6 @@ import {
Int32,
Int64,
Name,
P2P,
PermissionLevel,
PublicKey,
Signature,
Expand Down Expand Up @@ -115,6 +115,28 @@ suite('chain', function () {
assert.equal(blockId2.blockNum.equals(7), true)
})

test('blob', function () {
const expected = Bytes.from([0xbe, 0xef, 0xfa, 0xce])

// Correct
const string = 'vu/6zg=='
const blob = Blob.from(string)
assert.isTrue(Bytes.from(blob.array).equals(expected))

// Wrong padding, ensure it still works
const string2 = 'vu/6zg='
const blob2 = Blob.from(string2)
assert.isTrue(Bytes.from(blob2.array).equals(expected))

const string3 = 'vu/6zg'
const blob3 = Blob.from(string3)
assert.isTrue(Bytes.from(blob3.array).equals(expected))

const string4 = 'vu/6zg==='
const blob4 = Blob.from(string4)
assert.isTrue(Bytes.from(blob4.array).equals(expected))
})

test('bytes', function () {
assert.equal(Bytes.from('hello', 'utf8').toString('hex'), '68656c6c6f')
assert.equal(Bytes.equal('beef', 'beef'), true)
Expand Down
Loading