diff --git a/api/.eslintrc b/api/.eslintrc index 24ec14d55c..97e1aa8b3a 100644 --- a/api/.eslintrc +++ b/api/.eslintrc @@ -1,22 +1,44 @@ { + "root": true, "env": { "browser": true, "es2021": true, "node": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": ["@typescript-eslint"], + "plugins": [ + "@typescript-eslint" + ], "rules": { - "indent": ["warn", 2, { "SwitchCase": 1, "flatTernaryExpressions": false }], - "linebreak-style": ["error", "unix"], - "quotes": ["warn", "single"], - "semi": ["error", "always"], + "indent": [ + "warn", + 2, + { + "SwitchCase": 1, + "flatTernaryExpressions": false + } + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "warn", + "single" + ], + "semi": [ + "error", + "always" + ], "@typescript-eslint/no-empty-function": 0, "no-case-declarations": 0 } -} +} \ No newline at end of file diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index b8581bda9d..2662ff6e03 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.23.5 + +_07/18/2022_ + +https://github.com/gear-tech/gear-js/pull/843 + +### Changes + +- Fix getting program pages according to https://github.com/gear-tech/gear/pull/1193 + +--- ## 0.23.4 _07/18/2022_ diff --git a/api/package-lock.json b/api/package-lock.json index 6cdc990232..94dd1cd196 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gear-js/api", - "version": "0.23.4", + "version": "0.23.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@gear-js/api", - "version": "0.23.4", + "version": "0.23.5", "license": "GPL-3.0", "dependencies": { "@polkadot/api": "^8.11.3", diff --git a/api/package.json b/api/package.json index 3d4b52acf5..84b14777ad 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "@gear-js/api", - "version": "0.23.4", + "version": "0.23.5", "description": "A JavaScript library that provides functionality to connect GEAR Component APIs.", "main": "cjs/index.js", "module": "index.js", diff --git a/api/src/Code.ts b/api/src/Code.ts index 17ce2a693c..3099fd2101 100644 --- a/api/src/Code.ts +++ b/api/src/Code.ts @@ -4,7 +4,7 @@ import { Bytes, Option } from '@polkadot/types'; import { GearTransaction } from './Transaction'; import { generateCodeHash, validateCodeId } from './utils'; -import { CodeMetadata, Hex } from './types'; +import { CodeMetadata, CodeStorage, Hex } from './types'; export class GearCode extends GearTransaction { /** * Submit code without initialization @@ -20,8 +20,33 @@ export class GearCode extends GearTransaction { return { codeHash, submitted: this.submitted }; } + /** + * Check that codeId exists on chain + * @param codeId + * @returns + */ async exists(codeId: string) { const codeMetadata = await this.api.query.gearProgram.metadataStorage(codeId) as Option; return codeMetadata.isSome; } + + + /** + * Get code storage + * @param codeId + * @returns + */ + async storage(codeId: Hex): Promise { + return this.api.query.gearProgram.codeStorage(codeId) as unknown as CodeStorage; + } + + /** + * Get static pages of code + * @param codeId + * @returns + */ + async staticPages(codeId: Hex): Promise { + const storage = await this.storage(codeId); + return storage.isSome ? storage.unwrap().staticPages.toNumber() : null; + } } diff --git a/api/src/Program.ts b/api/src/Program.ts index abfc573097..1003bef2f2 100644 --- a/api/src/Program.ts +++ b/api/src/Program.ts @@ -1,6 +1,7 @@ import { AnyJson, ISubmittableResult } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { randomAsHex } from '@polkadot/util-crypto'; +import { u8aToHex } from '@polkadot/util'; import { Bytes } from '@polkadot/types'; import { createPayload, generateProgramId, GPROG, GPROG_HEX, validateGasLimit, validateValue } from './utils'; @@ -85,4 +86,16 @@ export class GearProgram extends GearTransaction { const program = progs.find((prog) => prog.eq(`0x${GPROG_HEX}${id.slice(2)}`)); return Boolean(program); } + + + + /** + * Get codeHash of program on-chain + * @param programId + * @returns codeHash in hex format + */ + async codeHash(programId: Hex): Promise { + const program = await this.api.storage.gProg(programId); + return u8aToHex(program.code_hash); + } } diff --git a/api/src/State.ts b/api/src/State.ts index 3e40b219b4..e59d3b4676 100644 --- a/api/src/State.ts +++ b/api/src/State.ts @@ -39,13 +39,18 @@ export class GearProgramState extends GearStorage { * @returns decoded state */ async read(programId: Hex, metaWasm: Buffer, inputValue?: AnyJson): Promise { + const codeHash = await this.api.program.codeHash(programId); + let initialSize = await this.api.code.staticPages(codeHash); + const program = await this.gProg(programId); - if (!program) { - throw new ReadStateError('Program is terminated'); - } + + program.allocations.forEach((value) => { + if (value.gtn(initialSize - 1)) { + initialSize = value.toNumber(); + } + }); const pages = await this.gPages(programId, program); - const initialSize = program.allocations.size; const block = await this.api.blocks.getFinalizedHead(); const blockTimestamp = await this.api.blocks.getBlockTimestamp(block.toHex()); diff --git a/api/src/Storage.ts b/api/src/Storage.ts index c5f6cb910f..0fe3b45e62 100644 --- a/api/src/Storage.ts +++ b/api/src/Storage.ts @@ -1,11 +1,10 @@ import { Option, Raw } from '@polkadot/types'; import { Codec } from '@polkadot/types/types'; -import { u8aToHex } from '@polkadot/util'; -import { IActiveProgram, IGearPages, IProgram, Hex } from './types'; +import { ActiveProgram, IGearPages, IProgram, Hex } from './types'; import { GPAGES_HEX, GPROG_HEX, SEPARATOR } from './utils'; import { CreateType } from './create-type'; -import { ReadStateError } from './errors'; +import { ProgramTerminatedError, ReadStateError } from './errors'; import { GearApi } from './GearApi'; export class GearStorage { @@ -21,13 +20,16 @@ export class GearStorage { * @param programId * @returns */ - async gProg(programId: Hex): Promise { + async gProg(programId: Hex): Promise { const storage = (await this.api.rpc.state.getStorage(`0x${GPROG_HEX}${programId.slice(2)}`)) as Option; if (storage.isNone) { throw new ReadStateError(`Program with id ${programId} was not found in the storage`); } const program = this.api.createType('Program', storage.unwrap()) as IProgram; - return program.isActive ? program.asActive : program.asTerminated; + + if (program.isTerminated) throw new ProgramTerminatedError(); + + return program.asActive; } /** @@ -36,7 +38,7 @@ export class GearStorage { * @param gProg * @returns */ - async gPages(programId: Hex, gProg: IActiveProgram): Promise { + async gPages(programId: Hex, gProg: ActiveProgram): Promise { const keys = {}; gProg.pages_with_data.forEach((value) => { keys[value.toNumber()] = `0x${GPAGES_HEX}${programId.slice(2)}${SEPARATOR}${this.api @@ -51,14 +53,4 @@ export class GearStorage { } return pages; } - - /** - * Get codeHash of program on-chain - * @param programId - * @returns codeHash in hex format - */ - async getCodeHash(programId: Hex): Promise { - const program = await this.gProg(programId); - return u8aToHex(program.code_hash); - } } diff --git a/api/src/errors/program.errors.ts b/api/src/errors/program.errors.ts index 71d2a95f32..db0a607399 100644 --- a/api/src/errors/program.errors.ts +++ b/api/src/errors/program.errors.ts @@ -6,6 +6,14 @@ export class SubmitProgramError extends Error { } } +export class ProgramDoesNotExistError extends Error { + name = 'ProgramDoesNotExist'; + + constructor() { + super('Program does not exist'); + } +} + export class GetGasSpentError extends Error { name = 'GetGasSpentError'; @@ -13,3 +21,11 @@ export class GetGasSpentError extends Error { super(`Unable to get gasSpent. ${message}` || 'Unable to get gasSpent. Params are invalid'); } } + +export class ProgramTerminatedError extends Error { + name = 'ProgramTerminated'; + + constructor() { + super('Program terminated'); + } +} \ No newline at end of file diff --git a/api/src/types/interfaces/message/entry.ts b/api/src/types/interfaces/message/entry.ts index a138cc71db..0c11040ab7 100644 --- a/api/src/types/interfaces/message/entry.ts +++ b/api/src/types/interfaces/message/entry.ts @@ -9,3 +9,5 @@ export interface Entry extends Enum { asHandle: Null; asReply: MessageId; } + +export type DispatchKind = Entry \ No newline at end of file diff --git a/api/src/types/interfaces/program/base.ts b/api/src/types/interfaces/program/base.ts index 53ce9b596e..84bb4adede 100644 --- a/api/src/types/interfaces/program/base.ts +++ b/api/src/types/interfaces/program/base.ts @@ -1,16 +1,17 @@ import { Enum, u32, u64, Map, BTreeSet } from '@polkadot/types'; import { MessageId } from '../ids'; +import { WasmPageNumber } from './pages'; export interface IProgram extends Enum { isActive: boolean; - asActive: IActiveProgram; + asActive: ActiveProgram; isTerminated: boolean; asTerminated: null; } -export interface IActiveProgram extends Map { - allocations: BTreeSet; +export interface ActiveProgram extends Map { + allocations: BTreeSet; pages_with_data: BTreeSet; code_hash: Uint8Array; nonce: u64; diff --git a/api/src/types/interfaces/program/code.ts b/api/src/types/interfaces/program/code.ts index f00e53a8b2..d8ef55bdbc 100644 --- a/api/src/types/interfaces/program/code.ts +++ b/api/src/types/interfaces/program/code.ts @@ -1,8 +1,19 @@ import { Codec } from '@polkadot/types-codec/types'; -import { H256 } from '@polkadot/types/interfaces'; -import { u32 } from '@polkadot/types'; +import { H256, } from '@polkadot/types/interfaces'; +import { u8, u32, Option, BTreeSet, Vec } from '@polkadot/types'; +import { WasmPageNumber } from './pages'; +import { DispatchKind } from '../message'; export interface CodeMetadata extends Codec { author: H256, blockNumber: u32 -} \ No newline at end of file +} + +export interface InstrumentedCode extends Codec { + code: Vec, + exports: BTreeSet, + staticPages: WasmPageNumber, + version: u32 +} + +export type CodeStorage = Option; \ No newline at end of file diff --git a/api/src/types/interfaces/program/pages.ts b/api/src/types/interfaces/program/pages.ts index 879dbf39c2..da8babd9c4 100644 --- a/api/src/types/interfaces/program/pages.ts +++ b/api/src/types/interfaces/program/pages.ts @@ -1,3 +1,7 @@ +import { u32 } from '@polkadot/types'; + export interface IGearPages { [key: string]: Uint8Array; } + +export type WasmPageNumber = u32; \ No newline at end of file diff --git a/api/test/State.test.ts b/api/test/State.test.ts index bf5c0a0119..f23be01d42 100644 --- a/api/test/State.test.ts +++ b/api/test/State.test.ts @@ -12,6 +12,7 @@ const demo_meta_test = { meta: readFileSync(join(GEAR_EXAMPLES_WASM_DIR, 'demo_meta.meta.wasm')), id: '0x' as Hex, uploadBlock: '0x', + codeHash: '0x' as Hex, }; const timestamp_test = { code: readFileSync(join(TEST_WASM_DIR, 'timestamp.opt.wasm')), @@ -28,7 +29,7 @@ beforeAll(async () => { gasLimit: 2_000_000_000, }).programId; let initStatus = checkInit(api, timestamp_test.id); - api.program.signAndSend(alice, () => {}); + api.program.signAndSend(alice, () => { }); expect(await initStatus()).toBe('success'); demo_meta_test.id = api.program.submit( @@ -69,6 +70,22 @@ describe('Read State', () => { expect(gPages).toBeDefined(); }); + test('Get codeHash', async () => { + demo_meta_test.codeHash = await api.program.codeHash(demo_meta_test.id); + expect(demo_meta_test.codeHash).toBeDefined(); + expect(demo_meta_test.codeHash.startsWith('0x')).toBeTruthy(); + }); + + test('Get code storage', async () => { + const codeStorage = await api.code.storage(demo_meta_test.codeHash); + expect(codeStorage.isSome).toBeTruthy(); + const unwrappedCodeStorage = codeStorage.unwrap().toHuman(); + expect(unwrappedCodeStorage).toHaveProperty('code'); + expect(unwrappedCodeStorage).toHaveProperty('exports'); + expect(unwrappedCodeStorage).toHaveProperty('staticPages'); + expect(unwrappedCodeStorage).toHaveProperty('version'); + }); + test('Get nonexistent program from storage', async () => { await expect( api.storage.gProg('0x0000000000000000000000000000000000000000000000000000000000000000'), diff --git a/package.json b/package.json index 59fe524db2..a729636bba 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "website/common" ], "lint-staged": { - "*.{js,css,ts,tsx,scss}": "eslint --fix" + "*.{js,css,ts,tsx,scss}": "eslint --fix", + "./api/*.{js,ts}": "cd api && npm run lint:fix" }, "resolutions": { "@types/react": "17.0.44"