Skip to content
This repository has been archived by the owner on Sep 11, 2023. It is now read-only.

CU-864dtwgww - Add a way to give hints when parsing #1

Merged
merged 2 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
283 changes: 277 additions & 6 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions packages/neo3-parser/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,107 @@
</p>

# Neo3-parser

Neo3-parser is a specification of how SmartContract client SDKs can interact with different parsing libraries such as Neon-JS.

Visit the [main page](../../README.md) of the project.

## How to parse responses

After invoking a contract you'll get the results on a stack. Use the `parseRpcResponse(field: RpcResponse, parseConfig?: ParseConfig)` function on each item to get the results parsed.
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you mean by "on each item"? It sounds like if I have multiple variables on my response I will need to do this for each of them, and this is not true, I can do this once for my response and it will parse everything.

Copy link
Author

Choose a reason for hiding this comment

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

Oh, I meant each item inside the result stack, e.g., if I invoked 3 methods on the same transaction, then the result stack would have length 3 and I would need to call parseRpcResponse 3 times.

I could overload the function and have a function parseRpcResponse(field: RpcResponse, parseConfig?: ParseConfig): any and a function parseRpcResponse(field: RpcResponse[], parseConfig?: ParseConfig[]): any[] if you prefer

Copy link
Contributor

Choose a reason for hiding this comment

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

In the majority of the use cases the user will invoke a single method on the transaction, so I think this overload is not necessary. I am just concern with the understanding. IDK, maybe saying "each item of the stack" might be enought to clarify.


### How to use parseConfig

The `parseConfig` object has the following properties:

| Property | Description |
|---------------------------|-------------------------------------------------------------------------------------------|
| type: string | a [valid ABI Type](https:/neo-project/proposals/blob/master/nep-14.mediawiki#method)|
| hint?: string | a type that extends from the ABI type |
| generic?: ParseConfig | you only need to pass this prop if `type` is `"Array"` |
| genericKey?: ParseConfig | you only need to pass this prop if `type` is `"Map"` |
| genericItem?: ParseConfig | you only need to pass this prop if `type` is `"Map"` |
| union?: ParseConfig[] | you only need to pass this prop if `type` is `"Any"` and you expect to get multiple types |

> Note: check [HINT_TYPES](./src/index.ts) to see what types are available to use on `hint`.

### Example

```ts
// Simulating a result stack after invoking a contract
const stackResult = [
{
type: "ByteString",
value: "AAECAwQFBgcICQoLDA0ODxAREhM=" // This value is a Hash160
}
]

// Call parseRpcResponse on an item of the stack
const response = Neo3Parser.parseRpcResponde(stackResult[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

you put parseRpcResponde with the D instead of S by mistake in several places 😅


console.log(response)
// Expected output: "☺☻♥♦♣♠\n♫☼►◄↕‼"

// You can use the `parseConfig` parameter to change how to parse the response
const responseHash160 = Neo3Parser.parseRpcResponde(stackResult[0], { type: "Hash160"})

console.log(responseHash160)
// Expected output: "0x131211100f0e0d0c0b0a09080706050403020100"

// Adding a hint might also change how it is parsed
const responseHash160LE = Neo3Parser.parseRpcResponde(stackResult[0], { type: "Hash160", hint: "ScriptHashLittleEndian"})

console.log(responseHash160LE)
// Expected output: "000102030405060708090a0b0c0d0e0f10111213"
```

#### Using neo3-boa to get the parseConfig

If you compiled your smart contract with Neo3-boa you can use the ABI inside the `.manifest.json` file as the `parseConfig`.

For example, compiling [this smart contract](https:/CityOfZion/neo3-boa/blob/d43c0a3cdb1db11e80093d8da8b30441384ba213/boa3_test/test_sc/generation_test/ManifestTypeHintFromUInt160ToScriptHashLittleEndian.py) will generate the following file:

```json
{
"name": "ManifestTypeHintFromUInt160ToScriptHashLittleEndian",
"groups": [],
"abi": {
"methods": [
{
"name": "Main",
"offset": 0,
"parameters": [],
"safe": false,
"returntype": "Hash160",
"returnhint": "ScriptHashLittleEndian"
}
],
"events": []
},
"permissions": [
{
"contract": "*",
"methods": "*"
}
],
"trusts": [],
"features": {},
"supportedstandards": [],
"extra": null
}
```

Then, you can copy the properties of the method you want to parse that have the `return` prefix, in this example it should be `returntype` and `returnhint`.
Remove the `return` prefix and attribute it to a `ParseConfig` variable in your TypeScript file.

```ts
const parseConfigFromNeo3boa = {
"type": "Hash160",
"hint": "ScriptHashLittleEndian"
}

const responseHash160LE = Neo3Parser.parseRpcResponde(stackResult[0], parseConfigFromNeo3boa)

console.log(responseHash160LE)
// Expected output: "000102030405060708090a0b0c0d0e0f10111213"
```
138 changes: 136 additions & 2 deletions packages/neo3-parser/dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,142 @@ export interface Neo3Parser {
* Formats the response from the RPC server to an easier to use format for dapp developers
* @param input The response from the RPC server
*/
parseRpcResponse: (field: any, parseConfig?: ParseConfig) => any;
parseRpcResponse: (field: RpcResponse, parseConfig?: ParseConfig) => any;
}
export interface ParseConfig {
ByteStringToScriptHash: boolean;
type: string;
hint?: string;
generic?: ParseConfig;
genericKey?: ParseConfig;
genericItem?: ParseConfig;
union?: ParseConfig[];
sessionId?: string;
}
export interface RpcResponse {
type?: string;
value: string | RpcResponse[] | RpcResponse | boolean;
key?: RpcResponse;
}
export declare const INTERNAL_TYPES: {
ARRAY: string;
BYTESTRING: string;
BUFFER: string;
INTEGER: string;
INTEROPINTERFACE: string;
BOOLEAN: string;
MAP: string;
NULL: string;
POINTER: string;
STRUCT: string;
};
export declare const ABI_TYPES: {
ANY: {
name: string;
};
SIGNATURE: {
name: string;
internal: string;
};
BOOLEAN: {
name: string;
internal: string;
};
INTEGER: {
name: string;
internal: string;
};
HASH160: {
name: string;
internal: string;
};
HASH256: {
name: string;
internal: string;
};
BYTEARRAY: {
name: string;
internal: string;
};
PUBLICKEY: {
name: string;
internal: string;
};
STRING: {
name: string;
internal: string;
};
ARRAY: {
name: string;
internal: string;
};
MAP: {
name: string;
internal: string;
};
INTEROPINTERFACE: {
name: string;
internal: string;
};
VOID: {
name: string;
internal: string;
};
};
export declare const EXTENDED_ABI_TYPES: {
ADDRESS: {
name: string;
abi: {
name: string;
internal: string;
};
};
PUBLICKEY: {
name: string;
abi: {
name: string;
internal: string;
};
};
SCRIPTHASH: {
name: string;
abi: {
name: string;
internal: string;
};
};
SCRIPTHASHLITTLEENDING: {
name: string;
abi: {
name: string;
internal: string;
};
};
BLOCKHASH: {
name: string;
abi: {
name: string;
internal: string;
};
};
TRANSACTIONID: {
name: string;
abi: {
name: string;
internal: string;
};
};
STORAGECONTEXT: {
name: string;
abi: {
name: string;
internal: string;
};
};
ITERATOR: {
name: string;
abi: {
name: string;
internal: string;
};
};
};
38 changes: 38 additions & 0 deletions packages/neo3-parser/dist/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EXTENDED_ABI_TYPES = exports.ABI_TYPES = exports.INTERNAL_TYPES = void 0;
exports.INTERNAL_TYPES = {
ARRAY: "Array",
BYTESTRING: "ByteString",
BUFFER: "Buffer",
INTEGER: "Integer",
INTEROPINTERFACE: "InteropInterface",
BOOLEAN: "Boolean",
MAP: "Map",
NULL: "Null",
POINTER: "Pointer",
STRUCT: "Struct",
};
exports.ABI_TYPES = {
ANY: { name: "Any" },
SIGNATURE: { name: "Signature", internal: exports.INTERNAL_TYPES.BYTESTRING },
BOOLEAN: { name: "Boolean", internal: exports.INTERNAL_TYPES.BOOLEAN },
INTEGER: { name: "Integer", internal: exports.INTERNAL_TYPES.INTEGER },
HASH160: { name: "Hash160", internal: exports.INTERNAL_TYPES.BYTESTRING },
HASH256: { name: "Hash256", internal: exports.INTERNAL_TYPES.BYTESTRING },
BYTEARRAY: { name: "ByteArray", internal: exports.INTERNAL_TYPES.BYTESTRING },
PUBLICKEY: { name: "PublicKey", internal: exports.INTERNAL_TYPES.BYTESTRING },
STRING: { name: "String", internal: exports.INTERNAL_TYPES.BYTESTRING },
ARRAY: { name: "Array", internal: exports.INTERNAL_TYPES.ARRAY },
MAP: { name: "Map", internal: exports.INTERNAL_TYPES.MAP },
INTEROPINTERFACE: { name: "InteropInterface", internal: exports.INTERNAL_TYPES.INTEROPINTERFACE },
VOID: { name: "Void", internal: exports.INTERNAL_TYPES.NULL },
};
exports.EXTENDED_ABI_TYPES = {
ADDRESS: { name: "Address", abi: exports.ABI_TYPES.STRING },
PUBLICKEY: { name: "PublicKey", abi: exports.ABI_TYPES.PUBLICKEY },
SCRIPTHASH: { name: "ScriptHash", abi: exports.ABI_TYPES.HASH160 },
SCRIPTHASHLITTLEENDING: { name: "ScriptHashLittleEndian", abi: exports.ABI_TYPES.HASH160 },
BLOCKHASH: { name: "BlockHash", abi: exports.ABI_TYPES.HASH256 },
TRANSACTIONID: { name: "TransactionId", abi: exports.ABI_TYPES.HASH256 },
STORAGECONTEXT: { name: "StorageContext", abi: exports.ABI_TYPES.INTEROPINTERFACE },
ITERATOR: { name: "Iterator", abi: exports.ABI_TYPES.INTEROPINTERFACE },
};
Loading