Skip to content

Commit

Permalink
[Typescript SDK] Fix data types for objects (MystenLabs#1693)
Browse files Browse the repository at this point in the history
* [Typescript SDK] Fix data types for objects

* [Typescript SDK] Simplify data structure
  • Loading branch information
666lcz authored May 1, 2022
1 parent 6f32a33 commit f9073cd
Showing 7 changed files with 477 additions and 13 deletions.
38 changes: 27 additions & 11 deletions sdk/typescript/src/index.guard.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
* Generated type guards for "index.ts".
* WARNING: Do not manually change this file.
*/
import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectRef, ObjectContent, ObjectOwner, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, SingleTransactionKind, TransactionKind, TransactionData, Transaction, EpochId, CertifiedTransaction, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature } from "./index";
import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectRef, ObjectContentField, ObjectContentFields, ObjectContent, ObjectOwner, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, SingleTransactionKind, TransactionKind, TransactionData, Transaction, EpochId, CertifiedTransaction, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature } from "./index";
import { BN } from "bn.js";

export function isEd25519KeypairData(obj: any, _argumentName?: string): obj is Ed25519KeypairData {
@@ -113,21 +113,37 @@ export function isObjectRef(obj: any, _argumentName?: string): obj is ObjectRef
)
}

export function isObjectContentField(obj: any, _argumentName?: string): obj is ObjectContentField {
return (
(isTransactionResponse(obj) as boolean ||
isSequenceNumber(obj) as boolean ||
obj === false ||
obj === true ||
Array.isArray(obj) &&
obj.every((e: any) =>
isSequenceNumber(e) as boolean
) ||
isObjectContent(obj) as boolean)
)
}

export function isObjectContentFields(obj: any, _argumentName?: string): obj is ObjectContentFields {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
Object.entries<any>(obj)
.every(([key, value]) => (isObjectContentField(value) as boolean &&
isTransactionResponse(key) as boolean))
)
}

export function isObjectContent(obj: any, _argumentName?: string): obj is ObjectContent {
return (
(obj !== null &&
typeof obj === "object" ||
typeof obj === "function") &&
(obj.fields !== null &&
typeof obj.fields === "object" ||
typeof obj.fields === "function") &&
Object.entries<any>(obj.fields)
.every(([key, value]) => ((isTransactionResponse(value) as boolean ||
isSequenceNumber(value) as boolean ||
value === false ||
value === true ||
isObjectContent(value) as boolean) &&
isTransactionResponse(key) as boolean)) &&
isObjectContentFields(obj.fields) as boolean &&
isTransactionResponse(obj.type) as boolean
)
}
3 changes: 2 additions & 1 deletion sdk/typescript/src/providers/json-rpc-provider.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ import {
ObjectRef,
TransactionDigest,
} from '../types';
import { transformGetObjectInfoResponse } from '../types/framework/transformer';

const isNumber = (val: any): val is number => typeof val === 'number';

@@ -56,7 +57,7 @@ export class JsonRpcProvider extends Provider {
[objectId],
isGetObjectInfoResponse
);
return resp;
return transformGetObjectInfoResponse(resp);
} catch (err) {
throw new Error(`Error fetching object info: ${err} for id ${objectId}`);
}
128 changes: 128 additions & 0 deletions sdk/typescript/src/types/framework/transformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isObjectContent } from '../../index.guard';
import {
getObjectContent,
GetObjectInfoResponse,
ObjectContent,
ObjectContentField,
ObjectContentFields,
ObjectExistsInfo,
} from '../objects';

/**
* Simplifies the common Move Object Content. This will be implemented
* in the Gateway server level after DevNet.
*/
export function transformGetObjectInfoResponse(resp: GetObjectInfoResponse) {
const content = getObjectContent(resp);
if (content != null) {
(resp.details as ObjectExistsInfo).object.contents = transformObjectContent(
content
);
}
return resp;
}

export function transformObjectContent(input: ObjectContent): ObjectContent {
let fields: ObjectContentFields = {};
Object.entries<any>(input.fields).forEach(([key, value]) => {
if (!isObjectContent(value)) {
fields[key] = value;
return;
}
const parsers: typeof MoveObjectContentTransformer[] = [
StringTransformer,
UniqueIDTransformer,
];
let isTransformed = false;
for (let p of parsers) {
if (p.canTransform(value)) {
fields[key] = p.toFieldValue(value);
isTransformed = true;
break;
}
}
if (!isTransformed) {
fields[key] = transformObjectContent(value);
}
});
return {
fields,
type: input.type,
};
}

abstract class MoveObjectContentTransformer {
static toFieldValue(_input: ObjectContent): ObjectContentField {
throw new Error('Children classes must override');
}

static canTransform(_input: ObjectContent): boolean {
throw new Error('Children classes must override');
}
}

class StringTransformer extends MoveObjectContentTransformer {
static toFieldValue(input: ObjectContent): ObjectContentField {
const bytes = input.fields['bytes'] as number[];
switch (input.type) {
case '0x1::ASCII::String':
return bytes.map(n => String.fromCharCode(n)).join('');
case '0x2::UTF8::String':
return stringFromUTF8Array(new Uint8Array(bytes))!;
}
return input;
}

static canTransform(input: ObjectContent): boolean {
return (
input.type === '0x2::UTF8::String' || input.type === '0x1::ASCII::String'
);
}
}

class UniqueIDTransformer extends MoveObjectContentTransformer {
static toFieldValue(input: ObjectContent): ObjectContentField {
if (UniqueIDTransformer.canTransform(input)) {
return (input.fields['id'] as ObjectContent).fields['bytes'];
}
return input;
}

static canTransform(input: ObjectContent): boolean {
return (
input.type === '0x2::ID::UniqueID' &&
isObjectContent(input.fields['id']) &&
input.fields['id'].type === '0x2::ID::ID'
);
}
}

// from https://weblog.rogueamoeba.com/2017/02/27/javascript-correctly-converting-a-byte-array-to-a-utf-8-string/
function stringFromUTF8Array(data: Uint8Array): string | null {
const extraByteMap = [1, 1, 1, 1, 2, 2, 3, 0];
var count = data.length;
var str = '';

for (var index = 0; index < count; ) {
var ch = data[index++];
if (ch & 0x80) {
var extra = extraByteMap[(ch >> 3) & 0x07];
if (!(ch & 0x40) || !extra || index + extra > count) return null;

ch = ch & (0x3f >> extra);
for (; extra > 0; extra -= 1) {
var chx = data[index++];
if ((chx & 0xc0) != 0x80) return null;

ch = (ch << 6) | (chx & 0x3f);
}
}

str += String.fromCharCode(ch);
}

return str;
}
27 changes: 26 additions & 1 deletion sdk/typescript/src/types/objects.ts
Original file line number Diff line number Diff line change
@@ -10,8 +10,17 @@ export type ObjectRef = {
version: number;
};

export type ObjectContentField =
| ObjectContent
| string
| boolean
| number
| number[];

export type ObjectContentFields = Record<string, ObjectContentField>;

export type ObjectContent = {
fields: Record<string, ObjectContent | string | boolean | number>;
fields: ObjectContentFields;
type: string;
};
export type ObjectOwner =
@@ -54,3 +63,19 @@ export type SequenceNumber = number;

// TODO: get rid of this by implementing some conversion logic from ObjectRef
export type RawObjectRef = [ObjectId, SequenceNumber, ObjectDigest];

/* ---------------------------- Helper functions ---------------------------- */

export function getObjectExistsResponse(
resp: GetObjectInfoResponse
): ObjectExistsInfo | undefined {
return resp.status !== 'Exists'
? undefined
: (resp.details as ObjectExistsInfo);
}

export function getObjectContent(
resp: GetObjectInfoResponse
): ObjectContent | undefined {
return getObjectExistsResponse(resp)?.object.contents;
}
Loading

0 comments on commit f9073cd

Please sign in to comment.