diff --git a/idl-parser/src/ffi/ast/mod.rs b/idl-parser/src/ffi/ast/mod.rs index afffd597..9526123c 100644 --- a/idl-parser/src/ffi/ast/mod.rs +++ b/idl-parser/src/ffi/ast/mod.rs @@ -1,18 +1,43 @@ use crate::ast; -use std::{slice, str}; +use std::{ + error::Error, + ffi::{c_char, CString}, + slice, str, +}; pub mod visitor; +#[repr(C)] +pub enum ParseResult { + Success(*mut Program), + Error(*const c_char), +} + /// # Safety /// /// See the safety documentation of [`slice::from_raw_parts`]. #[no_mangle] -pub unsafe extern "C" fn parse_idl(idl_ptr: *const u8, idl_len: u32) -> *mut Program { - let idl = unsafe { slice::from_raw_parts(idl_ptr, idl_len.try_into().unwrap()) }; - let idl = str::from_utf8(idl).unwrap(); - let program = ast::parse_idl(idl).unwrap(); - let program = Box::new(program); - Box::into_raw(program) +pub unsafe extern "C" fn parse_idl(idl_ptr: *const u8, idl_len: u32) -> *mut ParseResult { + let idl_slice = unsafe { slice::from_raw_parts(idl_ptr, idl_len as usize) }; + let idl_str = match str::from_utf8(idl_slice) { + Ok(s) => s, + Err(e) => return create_error(e, "validate idl_str"), + }; + + let program = match ast::parse_idl(idl_str) { + Ok(p) => p, + Err(e) => return create_error(e, "parse IDL"), + }; + + let program_box = Box::new(program); + let result = Box::new(ParseResult::Success(Box::into_raw(program_box))); + Box::into_raw(result) +} + +fn create_error(e: impl Error, context: &str) -> *mut ParseResult { + let err_str = CString::new(format!("{}: {}", context, e)).unwrap(); + let result = Box::new(ParseResult::Error(err_str.into_raw())); + Box::into_raw(result) } /// # Safety diff --git a/js/example/src/catalog.ts b/js/example/src/catalog.ts index c1edeb17..f93448b0 100644 --- a/js/example/src/catalog.ts +++ b/js/example/src/catalog.ts @@ -9,18 +9,20 @@ export type Part = | { slot: SlotPart }; export interface FixedPart { - z: number | string | null; + z: number | null; metadata_uri: string; } export interface SlotPart { - equippable: Array<`0x${string}` | Uint8Array>; - z: number | string | null; + equippable: Array; + z: number | null; metadata_uri: string; } export class RmrkCatalog { - private registry: TypeRegistry; + public readonly registry: TypeRegistry; + public readonly service: Service; + constructor(public api: GearApi, public programId?: `0x${string}`) { const types: Record = { Error: {"_enum":["PartIdCantBeZero","BadConfig","PartAlreadyExists","ZeroLengthPassed","PartDoesNotExist","WrongPartFormat","NotAllowedToCall"]}, @@ -32,6 +34,8 @@ export class RmrkCatalog { this.registry = new TypeRegistry(); this.registry.setKnownTypes({ types }); this.registry.register(types); + + this.service = new Service(this); } newCtorFromCode(code: Uint8Array | Buffer): TransactionBuilder { @@ -63,104 +67,114 @@ export class RmrkCatalog { this.programId = builder.programId; return builder; } +} - public addEquippables(part_id: number | string, collection_ids: Array<`0x${string}` | Uint8Array>): TransactionBuilder<{ ok: [number | string, Array<`0x${string}` | Uint8Array>] } | { err: Error }> { - return new TransactionBuilder<{ ok: [number | string, Array<`0x${string}` | Uint8Array>] } | { err: Error }>( - this.api, - this.registry, +export class Service { + constructor(private _program: RmrkCatalog) {} + + public addEquippables(part_id: number, collection_ids: Array): TransactionBuilder<{ ok: [number, Array] } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder<{ ok: [number, Array] } | { err: Error }>( + this._program.api, + this._program.registry, 'send_message', - ['AddEquippables', part_id, collection_ids], - '(String, u32, Vec<[u8;32]>)', + ['Service', 'AddEquippables', part_id, collection_ids], + '(String, String, u32, Vec<[u8;32]>)', 'Result<(u32, Vec<[u8;32]>), Error>', - this.programId + this._program.programId ); } - public addParts(parts: Record): TransactionBuilder<{ ok: Record } | { err: Error }> { - return new TransactionBuilder<{ ok: Record } | { err: Error }>( - this.api, - this.registry, + public addParts(parts: Record): TransactionBuilder<{ ok: Record } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder<{ ok: Record } | { err: Error }>( + this._program.api, + this._program.registry, 'send_message', - ['AddParts', parts], - '(String, BTreeMap)', + ['Service', 'AddParts', parts], + '(String, String, BTreeMap)', 'Result, Error>', - this.programId + this._program.programId ); } - public removeEquippable(part_id: number | string, collection_id: `0x${string}` | Uint8Array): TransactionBuilder<{ ok: [number | string, `0x${string}` | Uint8Array] } | { err: Error }> { - return new TransactionBuilder<{ ok: [number | string, `0x${string}` | Uint8Array] } | { err: Error }>( - this.api, - this.registry, + public removeEquippable(part_id: number, collection_id: string): TransactionBuilder<{ ok: [number, string] } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder<{ ok: [number, string] } | { err: Error }>( + this._program.api, + this._program.registry, 'send_message', - ['RemoveEquippable', part_id, collection_id], - '(String, u32, [u8;32])', + ['Service', 'RemoveEquippable', part_id, collection_id], + '(String, String, u32, [u8;32])', 'Result<(u32, [u8;32]), Error>', - this.programId + this._program.programId ); } - public removeParts(part_ids: Array): TransactionBuilder<{ ok: Array } | { err: Error }> { - return new TransactionBuilder<{ ok: Array } | { err: Error }>( - this.api, - this.registry, + public removeParts(part_ids: Array): TransactionBuilder<{ ok: Array } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); + return new TransactionBuilder<{ ok: Array } | { err: Error }>( + this._program.api, + this._program.registry, 'send_message', - ['RemoveParts', part_ids], - '(String, Vec)', + ['Service', 'RemoveParts', part_ids], + '(String, String, Vec)', 'Result, Error>', - this.programId + this._program.programId ); } - public resetEquippables(part_id: number | string): TransactionBuilder<{ ok: null } | { err: Error }> { + public resetEquippables(part_id: number): TransactionBuilder<{ ok: null } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); return new TransactionBuilder<{ ok: null } | { err: Error }>( - this.api, - this.registry, + this._program.api, + this._program.registry, 'send_message', - ['ResetEquippables', part_id], - '(String, u32)', + ['Service', 'ResetEquippables', part_id], + '(String, String, u32)', 'Result', - this.programId + this._program.programId ); } - public setEquippablesToAll(part_id: number | string): TransactionBuilder<{ ok: null } | { err: Error }> { + public setEquippablesToAll(part_id: number): TransactionBuilder<{ ok: null } | { err: Error }> { + if (!this._program.programId) throw new Error('Program ID is not set'); return new TransactionBuilder<{ ok: null } | { err: Error }>( - this.api, - this.registry, + this._program.api, + this._program.registry, 'send_message', - ['SetEquippablesToAll', part_id], - '(String, u32)', + ['Service', 'SetEquippablesToAll', part_id], + '(String, String, u32)', 'Result', - this.programId + this._program.programId ); } - public async equippable(part_id: number | string, collection_id: `0x${string}` | Uint8Array, originAddress: string, value?: number | string | bigint, atBlock?: `0x${string}`): Promise<{ ok: boolean } | { err: Error }> { - const payload = this.registry.createType('(String, u32, [u8;32])', ['Equippable', part_id, collection_id]).toU8a(); - const reply = await this.api.message.calculateReply({ - destination: this.programId, + public async equippable(part_id: number, collection_id: string, originAddress: string, value?: number | string | bigint, atBlock?: `0x${string}`): Promise<{ ok: boolean } | { err: Error }> { + const payload = this._program.registry.createType('(String, String, u32, [u8;32])', ['Service', 'Equippable', part_id, collection_id]).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId, origin: decodeAddress(originAddress), payload, value: value || 0, - gasLimit: this.api.blockGasLimit.toBigInt(), + gasLimit: this._program.api.blockGasLimit.toBigInt(), at: atBlock || null, }); - const result = this.registry.createType('(String, Result)', reply.payload); - return result[1].toJSON() as unknown as { ok: boolean } | { err: Error }; + const result = this._program.registry.createType('(String, String, Result)', reply.payload); + return result[2].toJSON() as unknown as { ok: boolean } | { err: Error }; } - public async part(part_id: number | string, originAddress: string, value?: number | string | bigint, atBlock?: `0x${string}`): Promise { - const payload = this.registry.createType('(String, u32)', ['Part', part_id]).toU8a(); - const reply = await this.api.message.calculateReply({ - destination: this.programId, + public async part(part_id: number, originAddress: string, value?: number | string | bigint, atBlock?: `0x${string}`): Promise { + const payload = this._program.registry.createType('(String, String, u32)', ['Service', 'Part', part_id]).toHex(); + const reply = await this._program.api.message.calculateReply({ + destination: this._program.programId, origin: decodeAddress(originAddress), payload, value: value || 0, - gasLimit: this.api.blockGasLimit.toBigInt(), + gasLimit: this._program.api.blockGasLimit.toBigInt(), at: atBlock || null, }); - const result = this.registry.createType('(String, Option)', reply.payload); - return result[1].toJSON() as unknown as Part | null; + const result = this._program.registry.createType('(String, String, Option)', reply.payload); + return result[2].toJSON() as unknown as Part | null; } } \ No newline at end of file diff --git a/js/example/yarn.lock b/js/example/yarn.lock index f52b8670..fa2f5201 100644 --- a/js/example/yarn.lock +++ b/js/example/yarn.lock @@ -514,10 +514,10 @@ bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -commander@12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.0.0.tgz#b929db6df8546080adfd004ab215ed48cf6f2592" - integrity sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA== +commander@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== create-require@^1.1.0: version "1.1.1" @@ -617,9 +617,9 @@ rxjs@^7.8.1: tslib "^2.1.0" "sails-js@file:../lib": - version "0.0.7" + version "0.1.4" dependencies: - commander "12.0.0" + commander "12.1.0" scale-ts@^1.6.0: version "1.6.0" diff --git a/js/src/app.ts b/js/src/app.ts index 05d530c8..b5efd5b9 100644 --- a/js/src/app.ts +++ b/js/src/app.ts @@ -25,6 +25,7 @@ const handler = async (path: string, out: string, name: string) => { generate(sails.parseIdl(idl), dir, file, name); } catch (e) { console.log(e.message, e.stack); + process.exit(1); } }; diff --git a/js/src/parser/parser.ts b/js/src/parser/parser.ts index b1238bf0..38140f95 100644 --- a/js/src/parser/parser.ts +++ b/js/src/parser/parser.ts @@ -255,10 +255,31 @@ export class WasmParser { this._idlLen = null; } + private readCString = (ptr: number): string => { + const view = new DataView(this._memory.buffer); + let len = 0; + while (view.getUint8(ptr + len) !== 0) { + len++; + } + const buf = new Uint8Array(this._memory.buffer, ptr, len); + return new TextDecoder().decode(buf); + }; + public parse(idl: string): Program { this.fillMemory(idl); - const programPtr = this._instance.exports.parse_idl(this._memPtr, this._idlLen); + const resultPtr = this._instance.exports.parse_idl(this._memPtr, this._idlLen); + + const view = new DataView(this._memory.buffer); + if (view.getUint32(resultPtr, true) == 1) { // Read ParseResult enum discriminant + // Error + const errorPtr = view.getUint32(resultPtr + 4, true); + const error = this.readCString(errorPtr); + + throw new Error(error); + } + + const programPtr = view.getUint32(resultPtr + 4, true); this._program = new Program(); this._instance.exports.accept_program(programPtr, 0);