🧙♂️Magic address
Get to know about the gateway to WASM and native env
Last updated
Get to know about the gateway to WASM and native env
Last updated
EVM and WASM are the dominant technologies in the cryptocurrency market. That is why KLY supports both EVM and WASM.
But you must admit that the project would not be complete if it were not possible to combine these 2 technologies and use them interchangeably. That is, for example, it would be cool if you could use some interesting algorithm in the EVM, the implementation of which is only available in Rust or Go, or, for example, call EVM logic directly from WASM smart contracts and vice versa.
In addition, since both VMs have access to the native runtime environment (we have Node.js since the KLY core has JS implementation), this means that both the EVM and WASM will have limited and controlled access to the JS environment. This means that it will be possible to expand both VMs by injecting the logic and functions of the native launch environment into them. Here's how it looks on the diagram
This brings us to the idea of hybrid smart contracts.
The EVM is a stack machine that runs low-level bytecode that has been sourced from Solidity, Vyper, or Yul.
Like many other languages, EVM bytecode includes a set of opcodes, each of which performs a certain action - puts something on the stack, reads something from the memory (heap), performs mathematical operations, and so on.
However, unlike classic assembler listings, there are no syscalls, access to the file system, random number generator or other things that can lead to non-determinism and disruption of the cryptocurrency node.
In fact, the EVM only imitates low-level work, and under the hood uses special functions that execute the logic of a specific opcode.
For example, the operation of the 0x56(jmp) opcode corresponds to the following function
Look at lines 5, 10 and 16. The function reads the top value from the simulated stack and removes it from there (pop), and then redefines the program counter to the specified address.
Here,for example, simple opcodes that allow you in the EVM to get the height of the block in which the contract is executed, the block time, its index, and so on:
So, everytime you call these in Solidity sources:
Previous functions will be executed
In order to combine EVM and WASM, we need to find such an opcode that would allow us to call a certain function with parameter passing and get the result.
Fortunately, the EVM has the ability to cross-contract calls. The result of the execution is then written to a variable in the form of raw bytes that can be decoded and get everything we need - the data structure, the result of the execution, and so on.
Let's experiment. Let's say we have 2 contracts - Caller and Receiver. As part of the tests, Caller makes a cross-contract call to Receiver.
And Receiver
The CALL opcode function has the following listing
By the way, it's good that this function is asynchronous because it will allow us to further give the EVM access to the Internet, read / write to the database, and so on.
The most interesting values here are the address toAddr and data - parameters and a function that needs to be called in the Receiver contract.
If we run this sequence
Deploy Receiver.sol
Deploy Caller.sol
Calling the testCallFoo function from the Caller.sol contract
And at the same time, we set a breakpoint in the debugger at the moment the CALL opcode is executed, we will see this:
Let's clear up a few things
Green part:
Here I demonstrate that at the level of the CALL opcode function, we managed to intercept the address of the contract being called. This will be needed later where we will talk about the magic address.
Red part:
In the red part, we are trying to read and understand the data that will be passed to the function.
If you look at the listing of the crossContractCallFromCallerToReceiver function(above), you will see that we encoded the parameters there. Next, we print it to the console (red step 1), then decode it (red step 2). In the end, I show that we get the same data values already inside the CALL function (red step 3).
This PoC shows the possibility that if we replace the behavior of the CALL opcode function, then we will be able to understand that the EVM wants to call a WASM or a function from the JS environment, as well as understand and decode the call parameters (strings, complex data structures, integer arguments, and so on )
After execution, we're going to push 1 to stack as th result of successful execution
After all, we would be interested in knowing how to write the result of a function to get it back to the EVM. Let's take a look at the writeCallOutput function. Continue debugging
We're in and ready for stepping
Line 178: Try to read raw returned data. As you see, we get the 32 bytes buffer filled with nulls and 124 as the last value.
It is because in function foo (see Receiver.sol) we return the incremented value of parameter. Since we pass the 123 it's obvious that we get 124 as result.
Now, let's check if we can rewrite the value before it will be returned to Caller.sol
As a reminder, here's the function with cross-contract call
The result of the execution(data) will be transferred to the log, so if we manage to correctly overwrite the data, then the EVM will continue to execute correctly, and in the logs we will get the overwritten value instead of 124.
Here we try to rewrite the 124 with 93 aa bytes. Now, let's finish. After logs decoding, check the console
Now, we can pre-set specific address to handle calls to WASM and JS(or other env).
For example:
0x00 - WASM
0x01 - JS
Once the modified EVM hook one of these addresses it's a signal to call a JS function or WASM contract.