-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
vm2 making browser build fail #152
Comments
FWIW, when I comment out the following code: esm/core/modules/impl/HandlerExecutionFactory.js - it works, I don't think there is a shim for NodeVM to run in the browser and for some reason it appears vite/esbuild is unable to remove it. import { Benchmark, LoggerFactory, MemCache, normalizeContractSource, SmartWeaveGlobal } from '../../..';
import { ContractHandlerApi } from './ContractHandlerApi';
import loader from '@assemblyscript/loader';
import { WasmContractHandlerApi } from './WasmContractHandlerApi';
import { asWasmImports } from './wasm/as-wasm-imports';
import { rustWasmImports } from './wasm/rust-wasm-imports';
import { Go } from './wasm/go-wasm-imports';
import BigNumber from 'bignumber.js';
//import { NodeVM, VMScript } from 'vm2';
class ContractError extends Error {
constructor(message) {
super(message);
this.name = 'ContractError';
}
}
/**
* A factory that produces handlers that are compatible with the "current" style of
* writing SW contracts (i.e. using "handle" function).
*/
export class HandlerExecutorFactory {
constructor(arweave) {
this.arweave = arweave;
this.logger = LoggerFactory.INST.create('HandlerExecutorFactory');
// TODO: cache compiled wasm binaries here.
this.cache = new MemCache();
}
async create(contractDefinition, evaluationOptions) {
const swGlobal = new SmartWeaveGlobal(this.arweave, {
id: contractDefinition.txId,
owner: contractDefinition.owner
}, evaluationOptions);
if (contractDefinition.contractType == 'wasm') {
this.logger.info('Creating handler for wasm contract', contractDefinition.txId);
const benchmark = Benchmark.measure();
let wasmInstance;
let jsExports = null;
const wasmResponse = generateResponse(contractDefinition.srcBinary);
switch (contractDefinition.srcWasmLang) {
case 'assemblyscript': {
const wasmInstanceExports = {
exports: null
};
wasmInstance = await loader.instantiateStreaming(wasmResponse, asWasmImports(swGlobal, wasmInstanceExports));
// note: well, exports are required by some imports
// - e.g. those that use wasmModule.exports.__newString underneath (like Block.indep_hash)
wasmInstanceExports.exports = wasmInstance.exports;
break;
}
case 'rust': {
const wasmInstanceExports = {
exports: null,
modifiedExports: {
wasm_bindgen__convert__closures__invoke2_mut__: null,
_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__: null
}
};
/**
* wasm-bindgen mangles import function names (adds some random number after "base name")
* - that's why we cannot statically build the imports in the SDK.
* Instead - we need to first compile the module and check the generated
* import function names (all imports from the "__wbindgen_placeholder__" import module).
* Having those generated function names - we need to then map them to import functions -
* see {@link rustWasmImports}
*
* That's probably a temporary solution - it would be the best to force the wasm-bindgen
* to NOT mangle the import function names - unfortunately that is not currently possible
* - https://github.com/rustwasm/wasm-bindgen/issues/1128
*/
const wasmModule = await getWasmModule(wasmResponse, contractDefinition.srcBinary);
const moduleImports = WebAssembly.Module.imports(wasmModule);
const wbindgenImports = moduleImports
.filter((imp) => {
return imp.module === '__wbindgen_placeholder__';
})
.map((imp) => imp.name);
const { imports, exports } = rustWasmImports(swGlobal, wbindgenImports, wasmInstanceExports, contractDefinition.metadata.dtor);
jsExports = exports;
wasmInstance = await WebAssembly.instantiate(wasmModule, imports);
wasmInstanceExports.exports = wasmInstance.exports;
const moduleExports = Object.keys(wasmInstance.exports);
// ... no comments ...
moduleExports.forEach((moduleExport) => {
if (moduleExport.startsWith('wasm_bindgen__convert__closures__invoke2_mut__')) {
wasmInstanceExports.modifiedExports.wasm_bindgen__convert__closures__invoke2_mut__ =
wasmInstance.exports[moduleExport];
}
if (moduleExport.startsWith('_dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__')) {
wasmInstanceExports.modifiedExports._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ =
wasmInstance.exports[moduleExport];
}
});
break;
}
case 'go': {
const go = new Go(swGlobal);
go.importObject.metering = {
usegas: function (value) {
swGlobal.useGas(value);
}
};
const wasmModule = await getWasmModule(wasmResponse, contractDefinition.srcBinary);
wasmInstance = await WebAssembly.instantiate(wasmModule, go.importObject);
// nope - DO NOT await here!
go.run(wasmInstance);
jsExports = go.exports;
break;
}
default: {
throw new Error(`Support for ${contractDefinition.srcWasmLang} not implemented yet.`);
}
}
this.logger.info(`WASM ${contractDefinition.srcWasmLang} handler created in ${benchmark.elapsed()}`);
return new WasmContractHandlerApi(swGlobal, contractDefinition, jsExports || wasmInstance.exports);
}
else {
this.logger.info('Creating handler for js contract', contractDefinition.txId);
const normalizedSource = normalizeContractSource(contractDefinition.src, evaluationOptions.useVM2);
if (!evaluationOptions.allowUnsafeClient) {
if (normalizedSource.includes('SmartWeave.unsafeClient')) {
throw new Error('Using unsafeClient is not allowed by default. Use EvaluationOptions.allowUnsafeClient flag.');
}
}
// if (evaluationOptions.useVM2) {
// const vmScript = new VMScript(normalizedSource);
// const vm = new NodeVM({
// console: 'off',
// sandbox: {
// SmartWeave: swGlobal,
// BigNumber: BigNumber,
// logger: this.logger,
// ContractError: ContractError,
// ContractAssert: function (cond, message) {
// if (!cond)
// throw new ContractError(message);
// }
// },
// compiler: 'javascript',
// eval: false,
// wasm: false,
// allowAsync: true,
// wrapper: 'commonjs'
// });
// return new ContractHandlerApi(swGlobal, vm.run(vmScript), contractDefinition);
// }
// else {
const contractFunction = new Function(normalizedSource);
const handler = contractFunction(swGlobal, BigNumber, LoggerFactory.INST.create(swGlobal.contract.id));
return new ContractHandlerApi(swGlobal, handler, contractDefinition);
//}
}
}
}
function generateResponse(wasmBinary) {
const init = { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/wasm' } };
return new Response(wasmBinary, init);
}
async function getWasmModule(wasmResponse, binary) {
if (WebAssembly.compileStreaming) {
return await WebAssembly.compileStreaming(wasmResponse);
}
else {
return await WebAssembly.compile(binary);
}
}
//# sourceMappingURL=HandlerExecutorFactory.js.map |
That's kind of weird as we explicitely tell browser environment to not include vm2 https://github.com/redstone-finance/redstone-smartcontracts/blob/main/package.json#L108 And it works fine in all our apps... eg. here https://github.com/redstone-finance/redstone-smartcontracts-app (tested in webpack-based apps but also in our web bundle-based apps) Tbh I'm not familiar with vite, so maybe there's an issue with this one? I believe even if you initialize smartweave like this
the issue persists, right? Anyway, I'll try to debug it after the weekend and test it with the custom smartweave client. |
Gosh, why all those 9827346 different JS build/bundle tools can't work consistently... Anyway, after googling for ~30seconds, it seems that rollup (which seems to be used by the Vite underneath) does not take the package.json's I.e. use this rollup plugin - https://github.com/rollup/plugins/tree/master/packages/node-resolve with probably this setting https://github.com/rollup/plugins/tree/master/packages/node-resolve#browser set to true... |
Just checked the source and the exact option you pointed out implement the correct spec to handle the There is a note in their doc:
Maybe the |
Have you tried using an alternative option of adding Setting If it won't work maybe you could use our web bundle in your app (https://github.com/redstone-finance/redstone-smartcontracts#using-web-bundles) and additionally maybe you could share your config and we could open an issue on rollup repo cause it seems a bit odd. |
I got it to work modifying the esbuild
And importing that script via an exports
Then this works great
Would you consider a PR that modifies the Both compile targets will go to the "bundles" folder and then we can add exports to the package.json
The reason I ask, is I am putting together workshops for smart weave contracts using modern ESM tooling with "VITE" and would love to not have to use the Thanks for your consideration, I am happy to submit a PR if this is an option. |
That's great! Although I'm not sure we should mark it as a final solution for vite users, I guess we should find a way to not use a dedicated bundle... But for now, a PR would be a blessing as we are quite busy these days :) |
closing for now, as we're switching to isolate-vm, which will probably a new set of issues 🤡 |
Building using vite and using
import { SmartWeave, Contract } from 'redstone-smartweave'
things from vm2 aren't affected by tree shaking so they are getting imported by rollup which fails to compile
Looks like it's always part of the runtime since it's being enabled through a toggle option. I want to put together a custom client like in the example but it seems that you still have to import
SmartWeave
which will in turn always importHandlerExecutorFactory
, so no import path to avoid node specific things.The text was updated successfully, but these errors were encountered: