Hybrid Compute
Introduction
Welcome to the developer documentation for implementing a basic example with our system and API. This guide will walk you through the necessary steps to get started and provide you with the information you need to successfully integrate our system into your project. Full Example
Overview
About Hybrid Compute
These are some basic information that might give you an easier time understanding this repository. Hybrid Compute in the Boba Network allows smart contracts to interact with external data and services. Typically, smart contracts on blockchains like Ethereum are limited to the data available on the blockchain, unable to access outside information directly. Hybrid Compute changes this by enabling smart contracts to make API calls to external services. This interaction allows smart contracts to access off-chain data and perform complex computations off-chain. The results of these computations can then be used on-chain, enhancing the functionality and efficiency of smart contracts. By doing so, Hybrid Compute reduces gas costs associated with complex computations and broadens the scope of what decentralized applications (dApps) can achieve. In essence, it bridges the gap between the blockchain and the real world, allowing for more sophisticated and dynamic applications.
About a Bundler
An Account Abstraction Bundler is a component of a blockchain system designed to enhance the functionality and flexibility of smart contract accounts. It facilitates the direct control of accounts by translating user intents, removing the traditional division between externally owned accounts and their dependencies for contract accounts. This abstraction allows users to interact more seamlessly and manage smart contract accounts directly. Additionally, the bundler aspect optimizes transaction packaging by grouping multiple transactions together before adding them to a block. This optimization can lead to more efficient use of block space and potentially lower transaction fees. Overall, Account Abstraction Bundlers enable the development of more sophisticated decentralized applications (dApps) by empowering smart contracts with greater control and functionality within the blockchain network
Prerequisites
Before you begin, make sure you have the following prerequisites in place:
Step 1: Writing the offchain handler
The first step is to write our offchain handler.
This handler is responsible for receiving requests from the bundler along with their payloads and returning the appropriate responses.
Let's begin with a simple example where the handler receives two numbers. It will perform both addition and subtraction on these numbers. If the result of the subtraction results in an overflow (i.e., the first number is greater than the second), the handler will respond with an underflow error.
First things first, we initialize an err_code
and a resp object with values in case of an exception:
In the try-block we parse the request with the help of this function:
and decode the reqBytes
to an array of [uin32, uint32]
since we want to receive two numbers.
Now we can perform our custom logic on the received values:
When the calculation was successful, we overwrite the previously created err_code and resp variables with successful values:
As we decoded the reqBytes
by letting the decode function know that we want to receive two numbers by giving it an array of ['uin32', 'uint32']
, we let the encode function know, on how we want to decode both numbers. In this case with ['uint256', 'uint256']
.
In case of an underflow error, we encode a string containing a text to let the user know what happened:
Before we can return these objects, we need to transform them into a specific object:
With that settled, we have successfuly implemented a function, which can receive a request from the bundler, perform some calculation with its payload and return a response.
Step 2: Setting up a server
With the offchain handler created, the next step is to set up a server to run it. In this example, we will create a simple JSON-RPC server using the jsonrpclib library. We define
Here, we register our function offchain_addsub2
. Note the "identifier" passed to register_function. This identifier is generated by the selector function, which creates a hashed representation of the function signature provided as an argument.
Why is that?
The bundler sends us an JSON-RPC v2 request, containing a method
identifier which is the function selector of the desired offchain method, along with several standard parameters (NOTE - names are subject to change) as well as a "payload" which contains the ABI-encoded request data:
In Step 3: Writing the Smart Contract we intialize a request object by encoding the function signature along with it's arguments.
So the JSON-RPC server maps the request to the actual function by the encoded signature.
Step 3: Writing the Smart Contract
Now we can write the Smart Contract, which will call our previously created offchain-handler. You can find the needed "HybridAccount"-Contract along with it's dependencies in the provided repository.
In the first part of the contract we are creating a mapping for the counters and we define a demoAddr
This address will then be part of the the HybridAccount
.
Now let us add the count method. We initialize the HybridAccount
with the demoAddr
created prior. We define x
and y
, do a quick check for b == 0
and encode our function addsub2()
. And the magic is going to happen within the HA.CallOffChain(userkey, req)
. This call will return us the two numbers a
and b
given the fact that there has been no error. If we encounter an error during the CallOffChain
call, we either revert or set the counter. See the two else if
statements for more information.
Starting in the "count"-function, we initialize an "HybridAccount" along the with the address used when we deployed the "Smart Contract"
Last but not least, we define a function countFail
as well as justemit
- which will be used to emit the event CalledFrom
.
And that's about it!
The "HybridAccount" contract has been previously registered to provide access to the "addsub2" function on our offchain-function. But more on that later.
Calling Offchain
As already mentioned in Step 2, our offchain-server maps the request, made by the bundler, via the hashed representation of our function-signature. So let's decode the function-signature we want to call on the offchain-server:
We then generate an userKey
by encoding "msg.sender". The userKey
parameter is used to distinguish requests so that they may be processed concurrently without interefering with each other.
Withing the Hybrid Account contract itself, the CallOffchain
method calls through to another system contract named HCHelper
:
In this example the HybridAccount implements a simple whitelist of contracts which are allowed to call its methods. It would also be possible for a HybridAccount to implement additional logic here, such as requiring payment of an ERC20 token to perform an offchain call. Or conversely, the owner of a HybridAccount could choose to make the CallOffchain method available to all callers without restriction.
There is an opportunity for a HybridAccount contract to implement a billing system here, requiring a payment of ERC20 tokens or some other mechanism of collecting payment from the calling contract. This is optional.
Helper Contract Implementation
In the code above, the contract checks an internal mapping to see if a response exists for the given request. If not then the method reverts with a special prefix, followed by an encoded version of the request parameters. If a response does exist then it is removed from the internal mapping and is returned to the caller. The map key encodes the request parameters, so that a response initiated by one request will not be returned later in response to a modified request from the caller.
To populate the response mapping, HybridAccount contracts use another method in the Helper:
Note that the msg.sender is included in the calculation of the internal map key, ensuring that only that HybridAccount is able to populate the response which it will later receive back in the TryCallOffchain()
call. However in the case of error results (success == false)
there is also a provision for the HC implementation to insert a result under a different map key.
PutResponse()
is called using Account Abstraction and the offchain userOperation must carry a valid signature in order for the operation to be executed.
Reading the response data
To retrieve the response from our AddSub
contract, we handle the offchain call as follows:
In this snippet, we decode the returned object ret
into two uint256
values, as the offchain function returns two integers. The variables x and y will hold the results of the addition and subtraction, respectively.
Step 4: Deploy the Smart Contract
With a few adjustments to the deploy.py script, we can easily deploy our newly created smart contract.
First, we need to configure the script to connect to our L1 and L2 networks:
Next, we load the contract using the loadContract function:
Here "path_prefix" indicates where the contract is located.
After loading the contract, we can deploy it as follows:
The constructor of our smart contract takes an address as an argument. Therefore, we pass the address of HybridAccount.1, which, along with other necessary contracts, is deployed as shown above.
Additional Examples
The documentation above was precisely written for the addition of two numbers.
The hybrid-compute
folder contains more examples that can be used and experimented with.
Let's integrate them into our server-loop
Last updated