Skip to content

Commit

Permalink
documentation & readme & rm try-catch
Browse files Browse the repository at this point in the history
  • Loading branch information
encody committed Dec 22, 2021
1 parent 370d9a9 commit 3346cfc
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 21 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# NEAR Contract Parser

Collection of utilities for parsing base64-encoded WASM smart contracts on [NEAR Protocol](https://near.org), extracting exported members, and detecting likely candidates for [standard contract interface implementation](https://nomicon.io/Standards/README.html).

# Usage

## Installation

```txt
$ npm install --save near-contract-parser
```

## Example

```js
const { Near, keyStores } = require('near-api-js');
const { parseContract } = require('near-contract-parser');

const near = new Near({
networkId: 'mainnet',
keyStore: new keyStores.InMemoryKeyStore(),
nodeUrl: 'https://rpc.mainnet.near.org',
archivalUrl: 'https://archival-rpc.mainnet.near.org',
walletUrl: 'https://wallet.mainnet.near.org',
helperUrl: 'https://helper.mainnet.near.org',
explorerUrl: 'https://explorer.mainnet.near.org',
});

(async () => {
const account_id = 'CONTRACT_ACCOUNT_ID.near';
const { code_base64 } = await near.connection.provider.query({
account_id,
finality: 'final',
request_type: 'view_code',
});

console.log(parseContract(code_base64));
})();
```

# Authors

- Jacob Lindahl <[email protected]> [@sudo_build](https://twitter.com/sudo_build)
3 changes: 3 additions & 0 deletions src/JsonType.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Data types supported by JSON format
*/
export type JsonType =
| 'string'
| 'number'
Expand Down
9 changes: 9 additions & 0 deletions src/ParsedContract.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { StandardInterfaceId } from './interfaces';

export interface ParsedContract {
/**
* Standard interfaces the original contract is likely to support
*/
probableInterfaces: StandardInterfaceId[];
/**
* Maps method names to the ID of the interface they are likey to constitute
*/
byMethod: Record<string, StandardInterfaceId[]>;
/**
* Names of functions exported from the original contract
*/
methodNames: string[];
}
8 changes: 8 additions & 0 deletions src/getProbableInterfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { StandardInterfaceId, interfaces } from './interfaces';

/**
* Guesses which standards a contract _probably_ implements based on exported
* method names.
*
* @param methodNames Exported method names
* @returns Interfaces the contract probably implements, as well as which
* methods likely constitute which interfaces
*/
export function getProbableInterfaces(methodNames: string[]): {
probableInterfaces: StandardInterfaceId[];
byMethod: Record<string, StandardInterfaceId[]>;
Expand Down
15 changes: 14 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { JsonType } from './JsonType';

export const enum StandardInterfaceId {
export enum StandardInterfaceId {
NEP141 = 'nep141',
NEP148 = 'nep148',
NEP171 = 'nep171',
NEP177 = 'nep177',
NEP178 = 'nep178',
}

/**
* Well-known smart contract interface specification
*/
export interface StandardInterface {
/** Unique interface ID */
id: StandardInterfaceId;
/** Human-readable interface name */
name: string;
/** Methods provided by interface */
methods: StandardInterfaceMethod[];
}

export interface StandardInterfaceArgument {
name: string;
/**
* An argument can support one or multiple types
* (e.g. Rust's `Option<String>` becomes `['string', 'null']`)
*/
type: JsonType | JsonType[];
}

Expand All @@ -24,6 +34,9 @@ export interface StandardInterfaceMethod {
args: StandardInterfaceArgument[];
}

/**
* Mapping interface ID to interface specification
*/
export const interfaces: Readonly<
Record<StandardInterfaceId, StandardInterface>
> = Object.freeze({
Expand Down
24 changes: 13 additions & 11 deletions src/methods.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { parseWasm } from './parseWasm';

/**
* Extracts exported functions from smart contract
*
* @param code_base64 Base64-encoded WASM binary (e.g. obtained from
* `near-api-js`)
* @returns List of exported function names
*/
export function getMethodNames(code_base64: string): string[] {
try {
const ast = parseWasm(code_base64);
return ast.body[0].fields
.filter(
(x: any) => x.type === 'ModuleExport' && x.descr.exportType === 'Func',
)
.map((x: any) => x.name) as string[];
} catch (e) {
console.error('Could not parse WASM', e);
return [];
}
const ast = parseWasm(code_base64);
return ast.body[0].fields
.filter(
(x: any) => x.type === 'ModuleExport' && x.descr.exportType === 'Func',
)
.map((x: any) => x.name) as string[];
}
6 changes: 0 additions & 6 deletions src/parseContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@ import { getProbableInterfaces } from './getProbableInterfaces';
import { ParsedContract } from './ParsedContract';
import { getMethodNames } from './methods';

const emptyContract: ParsedContract = {
probableInterfaces: [],
byMethod: {},
methodNames: [],
};

export function parseContract(code_base64: string): ParsedContract {
const methodNames = getMethodNames(code_base64);
const probableInterfaces = getProbableInterfaces(methodNames);
Expand Down
19 changes: 16 additions & 3 deletions src/parseWasm.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { decode } from '@webassemblyjs/wasm-parser';

function base64StringToUint8Array(str: string): Uint8Array {
/**
* Converts a base64-encoded string to a byte array. Works in browser and Node
* environments.
*
* @param strb64 base64 string
* @returns Decoded byte array
*/
function base64StringToUint8Array(strb64: string): Uint8Array {
if (typeof Buffer !== 'undefined') {
// Node
return Buffer.from(str, 'base64');
return Buffer.from(strb64, 'base64');
} else {
// Browser
return new Uint8Array(
atob(str.toString().trim())
atob(strb64.toString().trim())
.split('')
.map(c => c.charCodeAt(0)),
);
}
}

/**
* Parse base64-encoded WASM into AST
*
* @param wasmb64 base64-encoded WASM binary
* @returns WASM abstract syntax tree
*/
export function parseWasm(wasmb64: string): any {
return decode(base64StringToUint8Array(wasmb64));
}

0 comments on commit 3346cfc

Please sign in to comment.