diff --git a/src/__tests__/integration/data/wasm/counter.wasm b/src/__tests__/integration/data/wasm/counter.wasm index a392ab0..6d5c480 100644 Binary files a/src/__tests__/integration/data/wasm/counter.wasm and b/src/__tests__/integration/data/wasm/counter.wasm differ diff --git a/src/__tests__/integration/wasm-deploy-write-read.test.ts b/src/__tests__/integration/wasm-deploy-write-read.test.ts index 6402e54..6ec8474 100644 --- a/src/__tests__/integration/wasm-deploy-write-read.test.ts +++ b/src/__tests__/integration/wasm-deploy-write-read.test.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import ArLocal from 'arlocal'; import Arweave from 'arweave'; import { JWKInterface } from 'arweave/node/lib/wallet'; -import { Contract, LoggerFactory, SmartWeave, SmartWeaveNodeFactory } from '@smartweave'; +import {Contract, getTag, LoggerFactory, SmartWeave, SmartWeaveNodeFactory, SmartWeaveTags} from '@smartweave'; import path from 'path'; import { addFunds, mineBlock } from './_helpers'; @@ -68,7 +68,14 @@ describe('Testing the SmartWeave client for WASM contract', () => { it('should properly deploy contract', async () => { const contractTx = await arweave.transactions.get(contractTxId); + expect(contractTx).not.toBeNull(); + expect(getTag(contractTx, SmartWeaveTags.CONTRACT_TYPE)).toEqual('wasm'); + expect(getTag(contractTx, SmartWeaveTags.WASM_LANG)).toEqual('assemblyscript'); + + const contractSrcTx = await arweave.transactions.get(getTag(contractTx, SmartWeaveTags.CONTRACT_SRC_TX_ID)); + expect(getTag(contractSrcTx, SmartWeaveTags.CONTENT_TYPE)).toEqual('application/wasm'); + expect(getTag(contractSrcTx, SmartWeaveTags.WASM_LANG)).toEqual('assemblyscript'); }); it('should properly read initial state', async () => { @@ -122,8 +129,8 @@ describe('Testing the SmartWeave client for WASM contract', () => { function: 'infLoop' }); - expect(result.type).toEqual("exception"); - expect(result.errorMessage.startsWith("[RE:OOG")).toBeTruthy(); + expect(result.type).toEqual('exception'); + expect(result.errorMessage.startsWith('[RE:OOG')).toBeTruthy(); }); /*it('should skip interaction during contract state read if gas limit exceeded', async () => { @@ -139,6 +146,4 @@ describe('Testing the SmartWeave client for WASM contract', () => { expect(callStack.getInteraction(txId)).toEqual({}); });*/ - - }); diff --git a/src/core/ContractDefinition.ts b/src/core/ContractDefinition.ts index 45edd3b..87dc804 100644 --- a/src/core/ContractDefinition.ts +++ b/src/core/ContractDefinition.ts @@ -1,7 +1,7 @@ /** * This type contains all data and meta-data of the given contact. */ -import {ContractType} from "./modules/CreateContract"; +import { ContractType } from './modules/CreateContract'; export type ContractDefinition = { txId: string; diff --git a/src/core/SmartWeaveTags.ts b/src/core/SmartWeaveTags.ts index 9a449e0..226fa15 100644 --- a/src/core/SmartWeaveTags.ts +++ b/src/core/SmartWeaveTags.ts @@ -12,5 +12,7 @@ export enum SmartWeaveTags { MIN_FEE = 'Min-Fee', INIT_STATE = 'Init-State', INIT_STATE_TX = 'Init-State-TX', - INTERACT_WRITE = 'Interact-Write' + INTERACT_WRITE = 'Interact-Write', + WASM_LANG = 'Wasm-Lang', + CONTRACT_TYPE = 'Contract-Type' } diff --git a/src/core/modules/CreateContract.ts b/src/core/modules/CreateContract.ts index 3884c85..24bdfa3 100644 --- a/src/core/modules/CreateContract.ts +++ b/src/core/modules/CreateContract.ts @@ -29,6 +29,8 @@ export interface ContractData extends CommonContractData { export interface FromSrcTxContractData extends CommonContractData { srcTxId: string; + contractType: ContractType; + wasmLang: string | null; } export interface CreateContract { diff --git a/src/core/modules/impl/ContractDefinitionLoader.ts b/src/core/modules/impl/ContractDefinitionLoader.ts index e7ec9a5..eedfebc 100644 --- a/src/core/modules/impl/ContractDefinitionLoader.ts +++ b/src/core/modules/impl/ContractDefinitionLoader.ts @@ -12,9 +12,7 @@ import { import Arweave from 'arweave'; import Transaction from 'arweave/web/lib/transaction'; -const supportedSrcContentTypes = [ - 'application/javascript', 'application/wasm' -]; +const supportedSrcContentTypes = ['application/javascript', 'application/wasm']; export class ContractDefinitionLoader implements DefinitionLoader { private readonly logger = LoggerFactory.INST.create('ContractDefinitionLoader'); diff --git a/src/core/modules/impl/DefaultCreateContract.ts b/src/core/modules/impl/DefaultCreateContract.ts index 32e2cc1..b5aaa96 100644 --- a/src/core/modules/impl/DefaultCreateContract.ts +++ b/src/core/modules/impl/DefaultCreateContract.ts @@ -1,6 +1,15 @@ -import {ContractData, ContractType, CreateContract, FromSrcTxContractData, SmartWeaveTags} from '@smartweave/core'; +import { ContractData, ContractType, CreateContract, FromSrcTxContractData, SmartWeaveTags } from '@smartweave/core'; import Arweave from 'arweave'; import { LoggerFactory } from '@smartweave/logging'; +import { imports } from './wasmImports'; + +const wasmTypeMapping: Map = new Map([ + [1, 'assemblyscript'], + [2, 'rust'], + [3, 'go'], + [4, 'swift'], + [5, 'c'] +]); export class DefaultCreateContract implements CreateContract { private readonly logger = LoggerFactory.INST.create('DefaultCreateContract'); @@ -24,6 +33,27 @@ export class DefaultCreateContract implements CreateContract { srcTx.addTag(SmartWeaveTags.SDK, 'RedStone'); srcTx.addTag(SmartWeaveTags.CONTENT_TYPE, contractType == 'js' ? 'application/javascript' : 'application/wasm'); + let wasmLang = null; + + if (contractType == 'wasm') { + // note: instantiating wasm module for a while just to check + // the exported "type" value - which holds info about source lang. + const module = await WebAssembly.instantiate(src, dummyImports()); + // @ts-ignore + if (!module.instance.exports.type) { + throw new Error(`No info about source type in wasm binary. Did you forget to export global "type" value`); + } + // @ts-ignore + const type = module.instance.exports.type.value; + if (!wasmTypeMapping.has(type)) { + throw new Error(`Unknown wasm source type ${type}`); + } + + wasmLang = wasmTypeMapping.get(type); + + srcTx.addTag(SmartWeaveTags.WASM_LANG, wasmLang); + } + await this.arweave.transactions.sign(srcTx, wallet); this.logger.debug('Posting transaction with source'); @@ -34,6 +64,8 @@ export class DefaultCreateContract implements CreateContract { srcTxId: srcTx.id, wallet, initState, + contractType, + wasmLang, tags, transfer }); @@ -71,6 +103,10 @@ export class DefaultCreateContract implements CreateContract { contractTX.addTag(SmartWeaveTags.CONTRACT_SRC_TX_ID, srcTxId); contractTX.addTag(SmartWeaveTags.SDK, 'RedStone'); contractTX.addTag(SmartWeaveTags.CONTENT_TYPE, 'application/json'); + contractTX.addTag(SmartWeaveTags.CONTRACT_TYPE, contractData.contractType); + if (contractData.contractType == 'wasm') { + contractTX.addTag(SmartWeaveTags.WASM_LANG, contractData.wasmLang); + } await this.arweave.transactions.sign(contractTX, wallet); @@ -82,3 +118,12 @@ export class DefaultCreateContract implements CreateContract { } } } + +function dummyImports() { + return imports( + { + useGas: function () {} + } as any, + null + ); +} diff --git a/src/core/modules/impl/HandlerExecutorFactory.ts b/src/core/modules/impl/HandlerExecutorFactory.ts index 056b591..815f3b6 100644 --- a/src/core/modules/impl/HandlerExecutorFactory.ts +++ b/src/core/modules/impl/HandlerExecutorFactory.ts @@ -13,8 +13,8 @@ import { ContractHandlerApi } from './ContractHandlerApi'; import loader from '@assemblyscript/loader'; import { imports } from './wasmImports'; import { WasmContractHandlerApi } from './WasmContractHandlerApi'; +import metering from 'wasm-metering'; -const metering = require('wasm-metering'); /** * A factory that produces handlers that are compatible with the "current" style of * writing SW contracts (ie. using "handle" function). @@ -32,7 +32,7 @@ export class HandlerExecutorFactory implements ExecutorFactory { const wasmLogger = LoggerFactory.INST.create('WASM'); @@ -8,69 +8,74 @@ export const imports = (swGlobal: SmartWeaveGlobal, wasmModule: any): any => { usegas: swGlobal.useGas }, console: { - "console.log": function (msgPtr) { + 'console.log': function (msgPtr) { wasmLogger.debug(`${swGlobal.contract.id}: ${wasmModule.exports.__getString(msgPtr)}`); }, - "console.logO": function (msgPtr, objPtr) { - wasmLogger.debug(`${swGlobal.contract.id}: ${wasmModule.exports.__getString(msgPtr)}`, JSON.parse(wasmModule.exports.__getString(objPtr))); - }, + 'console.logO': function (msgPtr, objPtr) { + wasmLogger.debug( + `${swGlobal.contract.id}: ${wasmModule.exports.__getString(msgPtr)}`, + JSON.parse(wasmModule.exports.__getString(objPtr)) + ); + } }, block: { - "Block.height": function () { + 'Block.height': function () { return swGlobal.block.height; }, - "Block.indep_hash": function () { + 'Block.indep_hash': function () { return wasmModule.exports.__newString(swGlobal.block.indep_hash); }, - "Block.timestamp": function () { + 'Block.timestamp': function () { return swGlobal.block.timestamp; - }, + } }, transaction: { - "Transaction.id": function () { + 'Transaction.id': function () { return wasmModule.exports.__newString(swGlobal.transaction.id); }, - "Transaction.owner": function () { + 'Transaction.owner': function () { return wasmModule.exports.__newString(swGlobal.transaction.owner); }, - "Transaction.target": function () { + 'Transaction.target': function () { return wasmModule.exports.__newString(swGlobal.transaction.target); - }, + } }, contract: { - "Contract.id": function () { + 'Contract.id': function () { return wasmModule.exports.__newString(swGlobal.contract.id); }, - "Contract.owner": function () { + 'Contract.owner': function () { return wasmModule.exports.__newString(swGlobal.contract.owner); - }, + } }, api: { _readContractState: (fnIndex, contractTxIdPtr) => { const contractTxId = wasmModule.exports.__getString(contractTxIdPtr); const callbackFn = getFn(fnIndex); - console.log("Simulating read state of", contractTxId); + console.log('Simulating read state of', contractTxId); return setTimeout(() => { console.log('calling callback'); - callbackFn(wasmModule.exports.__newString(JSON.stringify({ - contractTxId - }))); + callbackFn( + wasmModule.exports.__newString( + JSON.stringify({ + contractTxId + }) + ) + ); }, 1000); }, - clearTimeout, + clearTimeout }, env: { abort(messagePtr, fileNamePtr, line, column) { - console.error("--------------------- Error message from AssemblyScript ----------------------"); - console.error(" " + wasmModule.exports.__getString(messagePtr)); - console.error( - ' In file "' + wasmModule.exports.__getString(fileNamePtr) + '"' - ); + console.error('--------------------- Error message from AssemblyScript ----------------------'); + console.error(' ' + wasmModule.exports.__getString(messagePtr)); + console.error(' In file "' + wasmModule.exports.__getString(fileNamePtr) + '"'); console.error(` on line ${line}, column ${column}.`); - console.error("------------------------------------------------------------------------------\n"); - }, + console.error('------------------------------------------------------------------------------\n'); + } } - } + }; function getFn(idx) { return wasmModule.exports.table.get(idx); diff --git a/tools/data/wasm/counter.wasm b/tools/data/wasm/counter.wasm index decd6c6..6d5c480 100644 Binary files a/tools/data/wasm/counter.wasm and b/tools/data/wasm/counter.wasm differ