Write a Smart Contract

To start our contract, we need to import the interface to our HybridAccount contract. The address of this HybridAccount is passed to the constructor and is stored as an immutable variable. Next we define other contract variables and helper functions.

The "counters" mapping provides a unique numeric counter scoped to each user who calls the contract.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "../samples/HybridAccount.sol";

contract TestHybrid {
    mapping(address => uint256) public counters;

    address payable immutable hcAccount;

    constructor(address payable _hcAccount) {
        hcAccount = _hcAccount;
    }
}

To implement our function to waste gas, we can leverage a for loop to increase transaction time:

  // helper method to waste gas
  // repeat - waste gas on writing storage in a loop
  // junk - dynamic buffer to stress the function size.
  mapping(uint256 => uint256) public xxx;
  uint256 public offset;

  function gasWaster(uint256 repeat, string calldata /*junk*/) external {
      for (uint256 i = 1; i <= repeat; i++) {
          offset++;
          xxx[offset] = i;
      }
  }

Now we can add the count() method. We initialize the HybridAccount with the hcAccount created prior, and allow for parameters a and b, our numbers to add and subtract together. We define x and y, do a quick check for b == 0, and encode our function offchain_addsub2(). We registered HybridAccount in the previous section to provide access to the offchain_addsub2() function on our off-chain function.

    function count(uint32 a, uint32 b) public {
        HybridAccount HA = HybridAccount(hcAccount);
        uint256 x;
        uint256 y;
        if (b == 0) {
            counters[msg.sender] = counters[msg.sender] + a;
            return;
        }

        bytes memory req = abi.encodeWithSignature("offchain_addsub2(uint32,uint32)", a, b);
        bytes32 userKey = bytes32(abi.encode(msg.sender));
        (uint32 error, bytes memory ret) = HA.CallOffchain(userKey, req);

The truly unique functionality of Hybrid Compute comes from HA.CallOffChain(userkey, req); this call will (assuming no error) return us the two numbers a and b after making computations off-chain The userKey parameter we pass in, generated by encoding msg.sender, distinguishes requests so that they may be processed concurrently without interefering with each other. As already mentioned in the previous section, our off-chain server maps the request made by the bundler via the hashed representation of our function-signature.

If we encounter an error during the CallOffChain() call, we either revert or set the counter forward by varying amounts. We can add these different cases in a series of else if blocks:

    function count(uint32 a, uint32 b) public {
        HybridAccount HA = HybridAccount(hcAccount);
        uint256 x;
        uint256 y;
        if (b == 0) {
            counters[msg.sender] = counters[msg.sender] + a;
            return;
        }

        bytes memory req = abi.encodeWithSignature("offchain_addsub2(uint32,uint32)", a, b);
        bytes32 userKey = bytes32(abi.encode(msg.sender));
        (uint32 error, bytes memory ret) = HA.CallOffchain(userKey, req);

        if (error == 0) {
            (x, y) = abi.decode(ret, (uint256, uint256)); // x=(a+b), y=(a-b)
            this.gasWaster(x, "abcd1234");
            counters[msg.sender] = counters[msg.sender] + y;
        } else if (b >= 10) {
            revert(string(ret));
        } else if (error == 1) {
            counters[msg.sender] = counters[msg.sender] + 100;
        } else {
            //revert(string(ret));
            counters[msg.sender] = counters[msg.sender] + 1000;
        }
    }

Notably, in the first if block, we decode the returned object ret into two uint256 values as the off-chain function returns two integers. The variables x and y hold the results of the addition and subtraction, respectively.

That finishes our smart contract! Proceed to the next section to learn how to deploy it.

Last updated