Skip to content
Open
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
11 changes: 11 additions & 0 deletions js/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CHANGELOG

## Unreleased

- Fixed: `decodeResult` in both `Sails` (v1) and `SailsProgram` (v2) now validates the
reply prefix against the expected service/method (v1) or Sails header interface_id
and entry_id (v2), matching the existing behavior of `decodePayload`. Passing
Comment thread
ukint-vs marked this conversation as resolved.
Outdated
headerless or mismatched reply bytes now throws a clear error instead of silently
producing garbage.
- Documented the `sails-js/types`, `sails-js/parser`, and `sails-js/util` subpath
exports in the README, which re-export types from the internal `sails-js-types`,
`sails-js-parser-idl-v2`, and `sails-js-util` packages.

## 0.5.1

- Fixed: generation correct TransactionBuilder constructor calls (https://github.com/gear-tech/sails/pull/1070).
Expand Down
15 changes: 15 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,21 @@ Use `sails.services.ServiceName.functions.FunctionName.encodePayload` method of
const payload = sails.services.ServiceName.functions.FunctionName.encodePayload(arg1, arg2);
```

### Subpath exports
Comment thread
ukint-vs marked this conversation as resolved.

In addition to the root `sails-js` entry, the package exposes a few subpath exports:

```javascript
// Shared TypeScript interfaces for parsed IDL types
// (e.g. ISailsTypeDef, ISailsPrimitiveDef, ISailsStructDef, ISailsEnumDef, ...)
import type { ISailsTypeDef, ISailsPrimitiveDef } from 'sails-js/types';

// Utility helpers (getScaleCodecDef, getPayloadMethod, ...)
import { getScaleCodecDef } from 'sails-js/util';
```

The `sails-js/types` subpath is useful for tooling that walks IDL type graphs (form renderers, custom decoders, IDE plugins) — the accessor interfaces (`isVec`/`asVec`, `isStruct`/`asStruct`, ...) are defined there and avoid the need for `any` casts on `TypeDef`/`PrimitiveDef` instances from `sails-js-parser`.


## Transaction builder

Expand Down
18 changes: 18 additions & 0 deletions js/READMEV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,24 @@ Use `program.services.ServiceName.functions.FunctionName.encodePayload` method o
const payload = program.services.ServiceName.functions.FunctionName.encodePayload(arg1, arg2);
```

### Subpath exports

`sails-js` re-exports a few utility modules via subpath exports. This lets you import parser/type/utility symbols without depending on internal workspace packages directly:

```javascript
// v2 parser (equivalent to the internal `sails-js-parser-idl-v2` package)
import { SailsIdlParser, SailsMessageHeader, InterfaceId, normalizeIdl } from 'sails-js/parser';

// Shared TypeScript interfaces describing parsed IDL types
// (e.g. ISailsTypeDef, ISailsPrimitiveDef, ISailsStructDef, ISailsEnumDef, ...)
import type { ISailsTypeDef, ISailsPrimitiveDef } from 'sails-js/types';

// Utility helpers (getScaleCodecDef, getPayloadMethod, ...)
import { getScaleCodecDef } from 'sails-js/util';
```

The `sails-js/types` subpath is particularly useful for tooling that walks IDL type graphs (form renderers, custom decoders, IDE plugins) — the accessor interfaces (`isVec`/`asVec`, `isStruct`/`asStruct`, ...) are defined there and avoid the need for `any` casts on `TypeDef`/`PrimitiveDef` instances.


## Transaction builder

Expand Down
1 change: 1 addition & 0 deletions js/src/sails-idl-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ export class SailsService implements ISailsService {
return result as T;
},
decodeResult: <T = any>(result: HexString) => {
_assertMatchingHeader(result, header, `${service.name}.${func.name} result`);
const payload = this.registry.createType(`([u8; 16], ${returnType})`, result);
return payload[1].toJSON() as T;
},
Expand Down
8 changes: 8 additions & 0 deletions js/src/sails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ export class Sails {
return result as T;
},
decodeResult: <T = any>(result: HexString) => {
const actualService = getServiceNamePrefix(result);
const actualFn = getFnNamePrefix(result);
if (actualService !== service.name || actualFn !== func.name) {
throw new Error(
`Invalid prefix for ${service.name}.${func.name} result: ` +
`got ${actualService}.${actualFn}`,
);
}
Comment thread
ukint-vs marked this conversation as resolved.
Outdated
const payload = this.registry.createType(`(String, String, ${returnType})`, result);
return payload[2].toJSON() as T;
},
Expand Down
15 changes: 15 additions & 0 deletions js/test/encode-decode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,19 @@ describe('Encode/Decode', () => {
expect(getServiceNamePrefix(walkEncoded)).toBe('Dog');
expect(getFunctionNamePrefix(walkEncoded)).toBe('Walk');
});

test('decodeResult validates service/function prefix', () => {
const add = sails.services.Counter.functions.Add;
const validReply = sails.registry
.createType('(String, String, u32)', ['Counter', 'Add', 99])
.toHex();
expect(add.decodeResult(validReply)).toBe(99);

const mismatchedReply = sails.registry
.createType('(String, String, u32)', ['WrongService', 'Add', 99])
.toHex();
expect(() => add.decodeResult(mismatchedReply)).toThrow(
'Invalid prefix for Counter.Add result',
);
});
});
46 changes: 46 additions & 0 deletions js/test/idl-v2-parser-type-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SailsIdlParser } from 'sails-js-parser-idl-v2';
import { hexToU8a } from '@polkadot/util';

import { SailsProgram } from '..';

Expand Down Expand Up @@ -341,3 +342,48 @@ describe('type-resolver-v2 generics', () => {
).toEqual({ three: { p1: arrayTupleU8, p2: [['a', 'b', 'c', 'd'], null] } });
});
});

describe('v2 decodeResult header validation', () => {
const idl = `
service Counter {
functions {
@entry-id: 0
Add(value: u32) -> u32;
@entry-id: 1
Sub(value: u32) -> u32;
}
}

program CounterProgram {
services {
Counter,
}
constructors {
Default();
}
}
`;

test('throws when result bytes have no valid Sails header', () => {
const program = new SailsProgram(parser.parse(idl));
const add = program.services.Counter.functions.Add;
// 16 zero bytes (no magic "GM") + a u32 — should fail header assertion.
const bogusResult = program.registry
.createType('([u8; 16], u32)', [new Uint8Array(16), 99])
.toHex();
expect(() => add.decodeResult(bogusResult)).toThrow(/Invalid Sails header/);
});

test("throws when header belongs to a different method's entry_id", () => {
const program = new SailsProgram(parser.parse(idl));
const add = program.services.Counter.functions.Add;
const sub = program.services.Counter.functions.Sub;
// Take Sub's (valid) 16-byte header from an encoded request payload.
// encodePayload(42) returns hex of ([u8; 16], u32); the first 16 bytes are Sub's header.
const subEncodedBytes = hexToU8a(sub.encodePayload(42));
const subHeader = subEncodedBytes.slice(0, 16);
// Use that header as the prefix for an "Add result" — interface_id matches but entry_id differs.
const mismatchedResult = program.registry.createType('([u8; 16], u32)', [subHeader, 99]).toHex();
expect(() => add.decodeResult(mismatchedResult)).toThrow(/Header mismatch/);
});
});
Loading