diff --git a/nix/sources.json b/nix/sources.json index b3ae03c8ca..a1e23b550f 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -40,7 +40,7 @@ "motoko": { "ref": "master", "repo": "ssh://git@github.com/dfinity-lab/motoko", - "rev": "788395608cdf75ecdff3c904bef52f2478467d5f", + "rev": "849cfba0b8dead6c6e5de0905491f9e33631eb32", "type": "git" }, "napalm": { diff --git a/src/userlib/js/bootstrap/candid/idl-ui.ts b/src/userlib/js/bootstrap/candid/idl-ui.ts index a79bfcc0e3..4fea62dc39 100644 --- a/src/userlib/js/bootstrap/candid/idl-ui.ts +++ b/src/userlib/js/bootstrap/candid/idl-ui.ts @@ -1,4 +1,4 @@ -import { IDL } from '@internet-computer/userlib'; +import { CanisterId, IDL } from '@internet-computer/userlib'; import BigNumber from 'bignumber.js'; // tslint:disable:max-classes-per-file @@ -7,7 +7,7 @@ class Render extends IDL.Visitor { public visitPrimitive(t: IDL.PrimitiveType, d: null): InputBox { return new InputBox(t, null); } - public visitUnit(t: IDL.UnitClass, d: null): InputBox { + public visitNull(t: IDL.NullClass, d: null): InputBox { const input = new InputBox(t, null); input.input.type = 'hidden'; return input; @@ -34,7 +34,7 @@ class Render extends IDL.Visitor { } class Parse extends IDL.Visitor { - public visitUnit(t: IDL.UnitClass, v: string): null { + public visitNull(t: IDL.NullClass, v: string): null { return null; } public visitBool(t: IDL.BoolClass, v: string): boolean { @@ -61,6 +61,9 @@ class Parse extends IDL.Visitor { public visitFixedNat(t: IDL.FixedNatClass, v: string): BigNumber { return new BigNumber(v); } + public visitPrincipal(t: IDL.PrincipalClass, v: string): CanisterId { + return CanisterId.fromText(v); + } } export function renderInput(t: IDL.Type): InputBox { diff --git a/src/userlib/js/src/canisterId.ts b/src/userlib/js/src/canisterId.ts index 8b7942d91b..2260b9a5f6 100644 --- a/src/userlib/js/src/canisterId.ts +++ b/src/userlib/js/src/canisterId.ts @@ -1,12 +1,17 @@ // Canister IDs are represented as an array of bytes in the HTTP handler of the client. export class CanisterId { - public static fromText(hex: string): CanisterId { - if (hex.startsWith('ic:')) { - // Remove the checksum from the hexadecimal. - // TODO: validate the checksum. - return this.fromHex(hex.slice(3, -2)); + public static fromText(text: string): CanisterId { + if (text.startsWith('ic:')) { + const hex = text.slice(3); + if (hex.length % 2 === 0 && /^[0-9A-F]+$/.test(hex)) { + // Remove the checksum from the hexadecimal. + // TODO: validate the checksum. + return this.fromHex(hex.slice(0, -2)); + } else { + throw new Error('Cannot parse canister id: ' + text); + } } else { - throw new Error('CanisterId not a "ic:" url: ' + hex); + throw new Error('CanisterId not a "ic:" url: ' + text); } } @@ -19,4 +24,7 @@ export class CanisterId { public toHex(): string { return this._idHex; } + public toText(): string { + return 'ic:' + this.toHex() + '00'; + } } diff --git a/src/userlib/js/src/idl.test.ts b/src/userlib/js/src/idl.test.ts index cfaa27ac27..2b240f4b51 100644 --- a/src/userlib/js/src/idl.test.ts +++ b/src/userlib/js/src/idl.test.ts @@ -1,5 +1,6 @@ // tslint:disable import BigNumber from 'bignumber.js'; +import { CanisterId } from './canisterId'; import * as IDL from './idl'; import { Buffer } from 'buffer/'; @@ -37,9 +38,9 @@ test('IDL encoding (empty)', () => { ); }); -test('IDL encoding (unit)', () => { +test('IDL encoding (null)', () => { // Null - test_(IDL.Unit, null, '4449444c00017f', 'Null value'); + test_(IDL.Null, null, '4449444c00017f', 'Null value'); }); test('IDL encoding (text)', () => { @@ -151,7 +152,7 @@ test('IDL encoding (array + tuples)', () => { // Nested Tuples test_( - IDL.Tuple(IDL.Tuple(IDL.Tuple(IDL.Tuple(IDL.Unit)))), + IDL.Tuple(IDL.Tuple(IDL.Tuple(IDL.Tuple(IDL.Null)))), [[[[null]]]], '4449444c046c01007f6c0100006c0100016c0100020103', 'Nested Tuples', @@ -212,6 +213,21 @@ test('IDL encoding (bool)', () => { expect(() => IDL.encode([IDL.Bool], ['false'])).toThrow(/Invalid bool argument/); }); +test('IDL encoding (principal)', () => { + // Principal + test_(IDL.Principal, CanisterId.fromText('ic:CAFFEE00'), '4449444c0001680103caffee', 'principal'); + test_( + IDL.Principal, + CanisterId.fromText('ic:000000000000000107'), + '4449444c00016801080000000000000001', + 'principal', + ); + expect(() => IDL.encode([IDL.Principal], ['ic:CAFFEE00'])).toThrow(/Invalid principal argument/); + expect(() => IDL.decode([IDL.Principal], Buffer.from('4449444c00016803caffee', 'hex'))).toThrow( + /Cannot decode principal/, + ); +}); + test('IDL encoding (variants)', () => { // Variants const Result = IDL.Variant({ ok: IDL.Text, err: IDL.Text }); @@ -224,7 +240,7 @@ test('IDL encoding (variants)', () => { // Test that nullary constructors work as expected test_( - IDL.Variant({ foo: IDL.Unit }), + IDL.Variant({ foo: IDL.Null }), { foo: null }, '4449444c016b01868eb7027f010000', 'Nullary constructor in variant', @@ -250,7 +266,7 @@ test('IDL encoding (variants)', () => { '4449444c026e7d6e000101010101', 'Nested option', ); - test_(IDL.Opt(IDL.Opt(IDL.Unit)), [[null]], '4449444c026e7f6e0001010101', 'Null option'); + test_(IDL.Opt(IDL.Opt(IDL.Null)), [[null]], '4449444c026e7f6e0001010101', 'Null option'); // Type description sharing test_( diff --git a/src/userlib/js/src/idl.ts b/src/userlib/js/src/idl.ts index 8d6447d670..c330d0e168 100644 --- a/src/userlib/js/src/idl.ts +++ b/src/userlib/js/src/idl.ts @@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'; import Pipe = require('buffer-pipe'); import { Buffer } from 'buffer/'; +import { CanisterId } from './canisterId'; import { JsonValue } from './types'; import { idlLabelToId } from './utils/hash'; import { lebDecode, lebEncode, slebDecode, slebEncode } from './utils/leb128'; @@ -26,6 +27,9 @@ const enum IDLTypeIds { Vector = -19, Record = -20, Variant = -21, + Func = -22, + Service = -23, + Principal = -24, } const magicNumber = 'DIDL'; @@ -95,7 +99,7 @@ export abstract class Visitor { public visitBool(t: BoolClass, data: D): R { return this.visitPrimitive(t, data); } - public visitUnit(t: UnitClass, data: D): R { + public visitNull(t: NullClass, data: D): R { return this.visitPrimitive(t, data); } public visitText(t: TextClass, data: D): R { @@ -113,6 +117,9 @@ export abstract class Visitor { public visitFixedNat(t: FixedNatClass, data: D): R { return this.visitPrimitive(t, data); } + public visitPrincipal(t: PrincipalClass, data: D): R { + return this.visitPrimitive(t, data); + } public visitConstruct(t: ConstructType, data: D): R { return this.visitType(t, data); @@ -265,9 +272,9 @@ export class BoolClass extends PrimitiveType { /** * Represents an IDL Null */ -export class UnitClass extends PrimitiveType { +export class NullClass extends PrimitiveType { public accept(v: Visitor, d: D): R { - return v.visitUnit(this, d); + return v.visitNull(this, d); } public covariant(x: any): x is null { @@ -873,7 +880,52 @@ export class RecClass extends ConstructType { } /** - * Represents an async function which can return data. + * Represents an IDL principal reference + */ +export class PrincipalClass extends PrimitiveType { + public accept(v: Visitor, d: D): R { + return v.visitPrincipal(this, d); + } + + public covariant(x: any): x is CanisterId { + return x instanceof CanisterId; + } + + public encodeValue(x: CanisterId): Buffer { + const hex = x.toHex(); + const buf = Buffer.from(hex, 'hex'); + const len = lebEncode(buf.length); + return Buffer.concat([Buffer.from([1]), len, buf]); + } + + public encodeType() { + return slebEncode(IDLTypeIds.Principal); + } + + public decodeValue(b: Pipe): CanisterId { + const x = b.read(1).toString('hex'); + if (x !== '01') { + throw new Error('Cannot decode principal'); + } + const len = lebDecode(b).toNumber(); + const hex = b + .read(len) + .toString('hex') + .toUpperCase(); + // TODO implement checksum + return CanisterId.fromText('ic:' + hex + '00'); + } + + get name() { + return 'principal'; + } + public valueToString(x: CanisterId) { + return x.toText(); + } +} + +/** + * Represents an IDL function reference. * @param argTypes Argument types. * @param retTypes Return types. * @param annotations Function annotations. @@ -994,16 +1046,13 @@ export function decode(retTypes: Type[], bytes: Buffer): JsonValue[] { * @param {Object} [fields] - a map of function names to IDL function signatures */ export class ActorInterface { - protected _id: Blob | null = null; - protected _batch: boolean = false; - constructor(public _fields: Record) {} } // Export Types instances. export const Empty = new EmptyClass(); export const Bool = new BoolClass(); -export const Unit = new UnitClass(); +export const Null = new NullClass(); export const Text = new TextClass(); export const Int = new IntClass(); export const Nat = new NatClass(); @@ -1018,6 +1067,8 @@ export const Nat16 = new FixedNatClass(16); export const Nat32 = new FixedNatClass(32); export const Nat64 = new FixedNatClass(64); +export const Principal = new PrincipalClass(); + export function Tuple(...types: T): TupleClass { return new TupleClass(types); }