ritual/projects/hello-world/contracts/Tutorial.md
2024-06-06 13:18:48 -04:00

6.4 KiB

GM! 🤠

In this tutorial we'll make a very simple consumer contract called SaysGm. All this contract does is request compute from our hello-world container and upon receiving a response, it prints everything to the console.

Note

Run this tutorial in a new directory, the end result of this tutorial will be pretty much the same as the contracts project, so refer to that if you get stuck.

Prerequisites

Installing foundry

You'll need foundry installed.

Scaffolding a new project

Create a new directory, and run forge init in it. This will create a new project with a foundry.yaml file in it.

mkdir says-gm
cd says-gm
forge init

Installing Infernet sdk

Install our Infernet SDK via forge.

forge install ritual-net/infernet-sdk

Specifying remappings

Create a new file called remappings.txt in the root of your project.

forge-std/=lib/forge-std/src
infernet-sdk/=lib/infernet-sdk/src

This'll make it easier to import our dependencies. More explanation on remappings here.

SaysGm contract

Under the src/ directory, create a new file called SaysGm.sol with the following content:

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.13;

import {console2} from "forge-std/console2.sol";
import {CallbackConsumer} from "infernet-sdk/consumer/Callback.sol";

contract SaysGM is CallbackConsumer {
    constructor(address registry) CallbackConsumer(registry) {}

    function sayGM() public {
        _requestCompute(
            "hello-world",
            bytes("Good morning!"),
            20 gwei,
            1_000_000,
            1
        );
    }

    function _receiveCompute(
        uint32 subscriptionId,
        uint32 interval,
        uint16 redundancy,
        address node,
        bytes calldata input,
        bytes calldata output,
        bytes calldata proof
    ) internal override {
        console2.log("\n\n"
        "_____  _____ _______ _    _         _\n"
        "|  __ \\|_   _|__   __| |  | |  /\\   | |\n"
        "| |__) | | |    | |  | |  | | /  \\  | |\n"
        "|  _  /  | |    | |  | |  | |/ /\\ \\ | |\n"
        "| | \\ \\ _| |_   | |  | |__| / ____ \\| |____\n"
        "|_|  \\_\\_____|  |_|   \\____/_/    \\_\\______|\n\n");


        console2.log("subscription Id", subscriptionId);
        console2.log("interval", interval);
        console2.log("redundancy", redundancy);
        console2.log("node", node);
        console2.log("input:");
        console2.logBytes(input);
        console2.log("output:");
        console2.logBytes(output);
        console2.log("proof:");
        console2.logBytes(proof);
    }
}

All this contract does is request compute from our hello-world container via the _requestCompute function. An Infernet node will pick up this subscription, execute the compute, and deliver the result to our contract via the _receiveCompute function.

Adding a Deploy Script

In the scripts directory, add a new file called Deploy.s.sol:


// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";
import {SaysGM} from "../src/SaysGM.sol";

contract Deploy is Script {
    function run() public {
        // Setup wallet
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        // Log address
        address deployerAddress = vm.addr(deployerPrivateKey);
        console2.log("Loaded deployer: ", deployerAddress);

        address registry = 0x663F3ad617193148711d28f5334eE4Ed07016602;
        // Create consumer
        SaysGM saysGm = new SaysGM(registry);
        console2.log("Deployed SaysHello: ", address(saysGm));

        // Execute
        vm.stopBroadcast();
        vm.broadcast();
    }
}

The coordinator address is the address of the Infernet coordinator. Our Infernet Anvil Node already has Coordinator pre-deployed to that address.

Adding a Call Script

Create another file under the script directory called CallContract.s.sol

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.0;

import {Script, console2} from "forge-std/Script.sol";
import {SaysGM} from "../src/SaysGM.sol";

contract CallContract is Script {
    function run() public {
        // Setup wallet
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        SaysGM saysGm = SaysGM(0x13D69Cf7d6CE4218F646B759Dcf334D82c023d8e);

        saysGm.sayGM();

        vm.stopBroadcast();
    }
}

Building the Project

Before building our project, we'll need to add this line to the foundry.toml file:

via_ir = true

So your foundry.toml file should look like this. Otherwise the compiler will complain about stack too deep errors.

Now, let's build our project.

forge build

The project should build successfully.

Deploying the Contracts

Deploy Infernet

To deploy our contracts, and later be able to call and test them, we'll need to deploy infernet, as well as our hello-world container! Refer to the readme at the root of this project for instructions on how to do that.

After deploying an Infernet Node locally, we'll need to run the Deploy script.

PRIVATE_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a \
  forge script script/Deploy.s.sol:Deploy --broadcast \
  --rpc-url http://localhost:8545

The private key here is anvil's anvil's third default address which contains 10000 ETH.

Calling the Contract

Similarly, to run our CallContract.s.sol script, we'll invoke it with forge script:

PRIVATE_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a \
  forge script script/CallContract.s.sol:Deploy --broadcast \
  --rpc-url http://localhost:8545

Using a Makefile

To make running these commands easier, we can add them to a Makefile. This allows us to run make deploy and make call instead of typing out the full command every time.

Refer to this project's Makefile for an example.

🎉 Done!

Congratulations! You've successfully created a contract that requests compute from our hello-world container.