ArweaveTeam / SmartWeave

Simple, scalable smart contracts on the Arweave protocol.
250 stars 76 forks source link

Running untrusted Javascript in SmartWeave contracts #77

Open joshbenaron opened 3 years ago

joshbenaron commented 3 years ago

Hey, I think we're going to need a way to run any JavaScript safely. This is going to be needed for certain things that are coming into the ecosystem such as AMM's. If we are to run random PST contracts in the browser we need to sandbox the code so it's 100% harmless. I wanted this to be the start of the discussion on the topic. I have a few ideas so far. Sandboxing can be done in two ways: 1) WASM/WASI inherently sandboxes execution. This won't be easy to do though as V8 (NodeJS JavaScript engine) doesn't support compiling to WASM 2) Running SmartWeave contracts in its own JS interpreter which can box up the code execution. See an example here: https://github.com/jterrace/js.js

Let me know your thoughts on this.

t8 commented 3 years ago

I'll add here that anything utilizing the foreign call protocol (which will hopefully be all SmartWeave token contracts in the future) would be at risk of this without a sandbox environment because users would be able to introduce any code into the execution path of the other contracts.

Technically users could whitelist the contracts that other contracts can read from, however, this naturally puts a large limitation on who can interact with what.

marcelsud commented 3 years ago

What about Quickjs? https://www.npmjs.com/package/quickjs-emscripten

littledivy commented 3 years ago

Here's a tiny solution for sandboxing contracts on Node.js (please test before using) -

diff --git a/src/contract-load.ts b/src/contract-load.ts
index 35c74fc..b700e5a 100644
--- a/src/contract-load.ts
+++ b/src/contract-load.ts
@@ -4,6 +4,7 @@ import { getTag, normalizeContractSource } from './utils';
 import { ContractHandler } from './contract-step';
 import { SmartWeaveGlobal } from './smartweave-global';
 import BigNumber from 'bignumber.js';
+import * as vm from "vm";

 /**
  * Loads the contract source, initial state and other parameters
@@ -65,11 +66,19 @@ export function createContractExecutionEnvironment(
   contractOwner: string,
 ) {
   const returningSrc = normalizeContractSource(contractSrc);
+
   const swGlobal = new SmartWeaveGlobal(arweave, { id: contractId, owner: contractOwner });
-  const getContractFunction = new Function(returningSrc); // eslint-disable-line
+  const getContractFunction = async (state, interaction) => {
+    let context = vm.createContext({ handle: null, swGlobal, BigNumber, clarity });
+    vm.runInContext(returningSrc, context, {
+      timeout: 10000,
+    });
+
+    return await context.handle(state, interaction);
+  }

   return {
-    handler: getContractFunction(swGlobal, BigNumber, clarity) as ContractHandler,
+    handler: getContractFunction as ContractHandler,
     swGlobal,
   };
 }
diff --git a/src/utils.ts b/src/utils.ts
index 54e1dbe..3823760 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -97,11 +97,9 @@ export function normalizeContractSource(contractSrc: string): string {
     .replace(/}\s*\)\s*\(\)\s*;/g, '');

   return `
-    const [SmartWeave, BigNumber, clarity] = arguments;
     clarity.SmartWeave = SmartWeave;
     class ContractError extends Error { constructor(message) { super(message); this.name = \'ContractError\' } };
     function ContractAssert(cond, message) { if (!cond) throw new ContractError(message) };
     ${contractSrc};
-    return handle;
   `;
 }

For browsers, the first thing that I can think of is using Web Workers and falling back to a QuickJS WASM interpreter.

joshbenaron commented 3 years ago

Hey @marcelsud @littledivy, thanks for the time you've put into this! As a community we've had a few calls on this topic and we're just working out what direction we should take this in. A lot of it seems to be going towards using WASM as a more portable and sandboxed solution. Feel free to discuss more with me and the community in the dev Discord: https://discord.gg/p8V3QC7J