Background
In Solidity, contract developers can create view functions for their contract to help clients to access contract state.
In Substrate, we have runtime APIs that achieve similar goal. That’s totally useable and a good enough solution. However, it still have a lot of room for improvement.
One of the nice property of the Solidity view function is that the same getter can be used for both contract logic and front-end and that reduces a lot of duplicated codes. For example, a rebase token exposes a balanceOf
function that returns the rebased token amount that can be changed for every block. Both other contracts and frontend/wallets are using the same balanceOf
API to query balances and does not need to care where is this number stored nor how it is calculated. It just works.
We lost this nice property in the current storage based API. Most of the Substrate UI are accessing the storage directly and sometimes have to reimplement the additional calculation logic to convert them to user friendly representation. This means duplicated code between the Rust runtime logic and UI JS/TS logic. The more code, the more work, and the more bug.
Runtime API partially address by allowing runtime developers to expose Wasm runtime API interface to call some Wasm code to get the right number. This avoids the duplicated code issue. However, it also lost a nice property of using storage based accessor: change subscription.
Polkadot.js and many other UI subscribes the storage changes and able to update the changes to user in real time. This allow users always see up-to-dated value, bots able to subscript for changes in realtime. This allow developers to build efficient and elegant applications easily.
By switching to Runtime API based accessor, pulling are required to detect change. For some applications that need to monitor many/all accounts, it will completely destroy the performance.
Solution
I am here (re)proposing an idea that was floating between discussion threads for many years and finally now feasible: Wasm view functions.
The goal is simple, take the best of both worlds.
We can avoid code duplication by reusing the same Rust code implemented in the runtime and compile them to wasm function and make then available to clients.
We can detect the accessed storage when executing the wasm function and subscribe those storages for change notifications. When any of those storages changed, rerun the wasm function and resubscribe to the storages.
In this way, we can for example implements a rebase token, and notify user every time the balances changed, no matter if it is triggered by block number of a transfer or some other storage changes. We have all the dependent storage for this property and can only subscribe the required storages, no more, no less.
Implementation
I had this idea for many years but it wasn’t really feasible back then. This have changed.
With Chopsticks and smoldot, it is proven possible to:
- Run wasm runtime in any modern JS environment
- Create a custom backend for the storages and connect it to the wasm host
To make this working e2e, we need:
- Rust framework to create Wasm accessor functions
- Custom wasm execution env similar to the one in Chopsticks and collect all the accessed storages within a wasm function
- Some helper JS library to put everything together