Idea: WASM Execution Engine in Cadence

[Full credit to @bastian for this idea! He mentioned the possibility in an off-handed way the other day, and it kind of blew my mind. Seems worth discussion with the rest of you!]

If you’ve looked at the Rust implementation of smart contracts on Cosmos or Solana, you’ll understand why the Flow team was pretty confident that WASM wasn’t the right solution for smart contracts generally. My thoughts on this haven’t changed at all in the intervening years; tracking ownership and access rights in Rust and Go simply is a good match for their design goals.

However!

As I see more projects looking into putting non-trivial logic in Cadence (like game logic or zero-knowledge proof checks), I think there might be value in using WASM for its raw algorithmic throughput. The same way you might import a numerical library written in C for use in Python, perhaps WASM is a great compliment to Cadence for certain workloads.

I don’t think the dev effort is extreme, either! There are a several chains (Cosmos, Polkadot, Multiverse) that use WASM and must have implementations that have some kind of execution effort metering. (I know the Ethereum community was working on this several years ago, but I haven’t heard much about it lately. Perhaps they moved on from that idea?)

And Cadence is designed to make it easy to publish “host hooks” so that Cadence code can call back into native libraries (it’s how crypto calls work from Cadence). No reworking necessary to make room for native callbacks.

The goal of this would be all about data processing and pure logic calculation. Passing Resource objects into the WASM runtime wouldn’t be safe, but a Resource object that used WASM logic to update its internal state would be very tractable. I think this could unlock a ton of performance for certain kinds of code without much disruption to the protocol.

I think an experienced Go engineer (or other systems development languages) that is new to the Flow project could reasonably take this work on with minimal support. I’m curious to get folks thoughts on this idea!

1 Like

Thank you for writing up this idea @dete! This summarizes my thoughts pretty well :+1:

For metering, there are two approaches: Injecting metering callbacks into the executed binary, and having metering support in the VM itself. The eWASM project (WASM for Ethereum) project implemented the former (Metering - Ethereum WebAssembly), and VMs like Wasmer support the latter (Wasmer Runtime Features).

Wasmer provides Go bindings, so it should be possible to leverage it and its metering functionality inside of the Cadence codebase fairly easily.

It should be fairly little work to design and implement a Cadence API that allows instantiating a WebAssembly module, and calling exported functions of it. WebAssembly only supports primitive integer types, which map well to Cadence’s integer types.

To allow higher-level values (structures, value-kinded arrays, strings, etc.) to be passed “into” and “back out” of the instance, we would also need a Cadence function to access the linear memory of the instance. This primitive could then be used to marshal Cadence values to bytes, transfer them into instance by writing the bytes to memory, performing some computation, and finally reading the bytes from the instance’s linear memory back on the Cadence side. It might make sense to use CCF for this purpose (in general, it might be useful to expose CCF inside of Cadence, but that’s just an aside).

Do we have a good example use-case / example WebAssembly program we would like to use inside of Cadence?

My mind is racing.

On the top of my head on an early saturday morning

  • sorting can be very complex gas wise, could that be done in wasm.

  • verifying zk proofs or did/ssi rc would work really well i imagine

1 Like

I love this idea, this can be really nice if we implement and meter it correctly.

Sounds interesting and worth exploring more.

This could enable Tokenized Computing Resources. A platform could enable users to rent out their computing resources through Wasm-based modules. Users could write and deploy their code for specific tasks, such as rendering, data analysis, or AI processing, and earn tokens in return.

If we could send HTTP requests we would also be able to do oracles, sync data from other chains or have dynamic NFTs change appearance based on off-chain events.

1 Like

I was on a quick vacation, had some time to think about this; this is a bit more tricky than initially I thought. Once we allow byte code; we are introducing a bit of a black box, even it is just code, how someone can verify a transaction touching a byte code ( wasm ) ? how will this effect FLIX ?

My thoughts on this haven’t changed at all in the intervening years; tracking ownership and access rights in Rust and Go simply is a good match for their design goals.

Totally agreed on this, risk of being broken record here, I think this should be on blockchain level, not language level.

Since working on some Cadence projects over a year ago, at Equilibrium we have completed a series of projects for other WASM-based smart contract platforms, as well as more generic WASM-based decentralized compute environments - just recently we started a partnership with ICP and it has been pretty eye-opening to see how far they have come.

I know in 20-21 the talk of WASM was pretty early / premature, but the game definitely has changed now - Cadence needs something like this.

Would love to contribute to discussions from our neutral, multi-chain and Rust/Go Expertise PoV.

Do you have an example for how a ZK proof can be verified?

What does “did/ssi rc” mean?

Providing a WebAssembly API in Cadence wouldn’t enable these use-cases, as the WebAssembly programs would still be executed as part of a transaction, which needs to have reasonable bounded execution time (e.g. not tasks like rendering, data analysis, etc.), and must be deterministic (e.g. cannot have non-deterministic code like making network calls).

Great point! The “black box” nature is definitely a disadvantage of bytecode. Just like developers should try to write readable code and avoid obfuscation, they should use this feature with the user in the mind, and only use it when necessary, preferring Cadence when possible.

Sorry for using tribe language.

did = decentralized identifiers
Ssi = self soverign identity
Rc was a typo of vc. So verifified credentials.

1 Like

For the API, I imagine there could be a built-in WebAssembly contract (similar to the existing RLP and BlS contracts), which would expose types and functions similar to the WebAssembly API in JavaScript (see e.g. Using the WebAssembly JavaScript API - WebAssembly | MDN).

If we expose the instance memory to the Cadence side, which would be necessary to pass complex data structures from the Cadence side into the WebAssembly instance and to read data back out from the instance, then a Cadence program could easily persist the instance’s state in account storage, i.e. keep state across multiple transactions.

Looking a bit more into use-cases: Solang is a Solidity compiler based on LLVM, which is able to produce WebAssembly binaries for Polkadot.
I haven’t found the ABI for Polkadot WebAssembly programs yet, but also have not looked to deep yet – it would be worth exploring if we could support the necessary imports/exports of the ABI.

In terms of protocol security, it would be an absolute necessity that the WASM runtime be fully deterministic. That way the existing Verification Nodes could recreate the computation and ensure it was done correctly. I would even propose that the first release would restrict WASM calls to being fully functional: dependent only on the data passed into them, with no ability to access global storage or call back into Cadence functions. (BTW- It’s possible we could relax this in future, but I think there’s still a lot of value in a compute-only initial release. A valuable MVP!)

In terms of FLIX, it has no impact, good or bad. Because the WASM code would be internal to smart contracts, FLIX continues to work as it currently does. This a great demonstration of the power of Capability-Based Security, BTW! The logic of the WASM code is limited to what capabilities we send it. If we restrict it to acting only on value types, it can’t mess with FTs, NFTs, Capabilities or anything else we would consider valuable and/or secure (which are wrapped in Resources).

I’d be cautious not to get too excited about this possibility. When people say they want “Solidity compatibility”, they don’t mean compatibility for the language, they mean they want to deploy EVM smart contracts, and have them work as expected. This means depending on the EVM and the Ethereum account model, not just the Solidity language.

In particular, “EVM compatibility” would mean Ethereum-style account management, smart contract data storage, Ethereum event handling, and transaction processing (nonces, signatures, EOAs). I’m not saying that a WASM runtime might not be helpful for this, and I’m certainly not saying we don’t want to find a way to provide EVM compatibility at some point. But let’s not pretend that adding a WASM runtime is a shortcut to EVM compatibility. It might get us closer, but more like 10% closer, not 90%… :sweat_smile:

1 Like

The WebAssembly support imagined/discussed here would help with the language part, i.e. provide e.g. some means to port code. As you mentioned, the aim should not be to provide full support for an environment that would allow unmodified Solidity contracts to run as-is.

For example, for the targets that Solang supports, Polkadot and Solana, there are several limitations / differences to Solidity on Ethereum:

I can’t say how useful it would be to run some modified Solidity code in Cadence, but wanted to mentioned it as one of the use-cases of the feature, as I find it exciting to open up Flow/Cadence to more languages and use-cases.

1 Like

Ah ok this is totally cool. I was more like thinking writing the smart contract in wasm ( or a part of it ) then other contracts or code can call them.

That would prbly make sense, the problem is passing a massive array to any function is still intensive gas wise due to nothing being reference types outside of resources.

Love the Idea!

This means that more complex computational logic can be implemented through Wasm, but data storage still exists in the form of Resources in Cadence.

The first question that comes to mind is the accessibility of Resource data in Wasm, as this involves capabilities and access control in Cadence.
If the use of wasm is limited to only input/output, its use cases may be restricted.

It would be best if there was a way to read an injected accessor for Resources within the context of wasm.

1 Like

Took a couple hours yesterday to create a PoC: https://github.com/onflow/cadence/pull/2760.

What’s left is mostly defining metering and limits. See e.g. what’s possible in wasmtime, https://pkg.go.dev/github.com/bytecodealliance/wasmtime-go: It has the concept of “fuel” and limits for e.g. stack depth. Ideally we should define the values in such a way that we do not tie the API to a particular VM, like here, wasmtime, so we can switch to a different implementation if needed.

Metering is the most critical part I guess; if we make things cheaper in wasm vs cadence, then people will move stuff from Cadence to Wasm ( Cadence parts will be calling wasm ) which I think we don’t want at all.

But also as wasm will be less optimized than Cadence in ideal world, this is really tricky. ( One of the options would be Cadence emitting wasm, but this expands the scope a lot )