From c2e9338aeb11b2a953b9e2aa0b1b7a8e8f5f8b77 Mon Sep 17 00:00:00 2001 From: Samuel Manzanera Date: Wed, 11 Sep 2024 22:24:26 +0200 Subject: [PATCH] use manifest to contract --- example/transactionBuilder/app.js | 7 +-- src/api.ts | 4 +- src/contract.ts | 90 ++++++++++++++++--------------- src/transaction_builder.ts | 25 +-------- tests/transaction_builder.test.ts | 7 --- 5 files changed, 53 insertions(+), 80 deletions(-) diff --git a/example/transactionBuilder/app.js b/example/transactionBuilder/app.js index d04fbad..694007e 100644 --- a/example/transactionBuilder/app.js +++ b/example/transactionBuilder/app.js @@ -249,14 +249,11 @@ let namedParams = []; window.onChangeRecipient = async () => { const address = document.querySelector("#recipient").value; - const contractCode = await archethic.network.getContractCode(address); - if (contractCode == "") { - return; - } + const contractContext = await archethic.network.getContractCode(address); document.querySelector("#namedActionsContainer").style.display = "block"; - const actions = await Contract.extractActionsFromContract(contractCode); + const actions = await Contract.extractActionsFromContract(contractContext); actions.forEach((action) => { const option = document.createElement("option"); option.text = action.name; diff --git a/src/api.ts b/src/api.ts index 828acc3..5d86987 100644 --- a/src/api.ts +++ b/src/api.ts @@ -355,9 +355,9 @@ export async function getContractCode(address: string | Uint8Array, endpoint: st .then(handleResponse) .then((res): string => { if (res.errors || res.data == null) { - return ""; + throw new Error("No contract at this address") } else { - return res.data.lastTransaction.data.code; + return res.data.lastTransaction.data.code } }); } diff --git a/src/contract.ts b/src/contract.ts index 29c6111..bb89190 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -2,59 +2,65 @@ import { ContractAction } from "./types.js"; import { encryptSecret, deriveAddress } from "./crypto.js"; import { ExtendedTransactionBuilder } from "./transaction.js"; import Archethic from "./index.js"; -import { hexToUint8Array, isHex } from "./utils.js"; +import { isHex } from "./utils.js"; -export async function extractActionsFromContract(code: string): Promise { - let actions = []; +type CodeWithManifest = { + bytecode: string; + manifest: WasmManifest +} - if (isHex(code)) { - const wasmModule = await WebAssembly.instantiate(hexToUint8Array(code), { - "archethic/env": { - log: (offset: bigint, length: bigint) => {}, - store_u8: (offset: bigint, data: bigint) => {}, - load_u8: (offset: bigint): number => 0, - input_size: (): bigint => 0n, - alloc: (length: bigint): bigint => 0n, - set_output: (offset: bigint, length: bigint) => {}, - set_error: (offset: bigint, length: bigint) => {} - }, - // FIXME with JSON RPC like request - "archethic/IO": { - get_balance: (offset: bigint, length: bigint) => 0 - } - }); +type WasmManifest = { + abi: WasmABI +} - const reservedFunctions = ["spec", "init", "onUpgrade"]; - for (let key in wasmModule.instance.exports) { - if (wasmModule.instance.exports[key] instanceof Function) { - if (!reservedFunctions.includes(key)) { - actions.push({ name: key, parameters: ["WASM JSON Input"] }); - } - } - } +type WasmABI = { + functions: Record +} - actions.push({ name: "upgrade", parameters: ['WASM JSON Input ( {"code": "wasm code as hex"})'] }); +type WASMFunctionABI = { + type: string; + triggerType?: string; + name: string; + input: Record +} - return actions; +export async function extractActionsFromContract(code: string): Promise { + try { + const codeWithManifest: CodeWithManifest = JSON.parse(code) + const manifest = codeWithManifest.manifest + let actions: ContractAction[] = [] + for (let name of Object.keys(manifest.abi.functions)) { + const functionABI = manifest.abi.functions[name] + if (functionABI.type == "action" && functionABI.triggerType == "transaction") { + actions.push({ + name: name, + parameters: functionABI.input ? Object.keys(functionABI.input) : [] + }) + } + } + return actions } + catch(e) { + let actions = []; - const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; + const regex = /actions\s+triggered_by:\s+transaction,\s+on:\s+([\w\s.,()]+?)\s+do/g; - for (const match of code.matchAll(regex)) { - const fullAction = match[1]; + for (const match of code.matchAll(regex)) { + const fullAction = match[1]; - const regexActionName = /(\w+)\((.*?)\)/g; - for (const actionMatch of fullAction.matchAll(regexActionName)) { - const name = actionMatch[1]; - const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; - actions.push({ - name: name, - parameters: parameters - }); + const regexActionName = /(\w+)\((.*?)\)/g; + for (const actionMatch of fullAction.matchAll(regexActionName)) { + const name = actionMatch[1]; + const parameters = actionMatch[2] != "" ? actionMatch[2].split(",") : []; + actions.push({ + name: name, + parameters: parameters + }); + } } - } - return actions; + return actions; + } } export function parseTypedArgument(input: any): any { diff --git a/src/transaction_builder.ts b/src/transaction_builder.ts index 9acc8d2..06fc718 100644 --- a/src/transaction_builder.ts +++ b/src/transaction_builder.ts @@ -433,36 +433,13 @@ export default class TransactionBuilder { * JSON RPC API SEND_TRANSACTION */ async toNodeRPC(): Promise { - async function asyncConcatUint8Arrays(uint8arrays: Uint8Array[]) { - const blob = new Blob(uint8arrays); - const buffer = await blob.arrayBuffer(); - return new Uint8Array(buffer); - } - - async function compress(str: Uint8Array): Promise { - // Convert the string to a byte stream. - const stream = new Blob([str]).stream(); - - // Create a compressed stream. - const compressedStream = stream.pipeThrough(new CompressionStream("deflate-raw")); - - // Read all the bytes from this stream. - const chunks: Uint8Array[] = []; - //@ts-ignore - for await (const chunk of compressedStream) { - chunks.push(chunk); - } - - return await asyncConcatUint8Arrays(chunks); - } - return { version: this.version, address: uint8ArrayToHex(this.address), type: this.type, data: { content: new TextDecoder().decode(this.data.content), - code: uint8ArrayToHex(await compress(this.data.code)), + code: new TextDecoder().decode(this.data.code), ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), diff --git a/tests/transaction_builder.test.ts b/tests/transaction_builder.test.ts index ef224cb..4099fd2 100644 --- a/tests/transaction_builder.test.ts +++ b/tests/transaction_builder.test.ts @@ -603,11 +603,4 @@ describe("Transaction builder", () => { expect(txRPC.data.content).toStrictEqual("Hello world"); }); }); - - describe("toNodeRPC", () => { - it("should compress using zlib contract's code", async () => { - const tx = new TransactionBuilder("code").setCode("0061736d01000000015e1160017f017f60067f7f7f7f7f7f0060037f7f7f"); - console.log(await tx.toNodeRPC()); - }); - }); });