🛠ī¸Create Your First dAPP using Foundry

Foundry is a smart contract development toolchain and manages your dependencies, compiles your project, runs tests, deploys, and lets you interact with the chain from the command-line and via Solidity scripts. Foundry also allow you to simulate development on the Sepolia network including Solidity native testing.

Note: that the FHE operations are only behavior simulations and may not accurately reflect actual system performance, gas consumption, etc.

Getting Started

First, create a new project, and then initialize the Foundry project using the forge init command. The default template comes with one dependency installed: Forge Standard Library. This is the preferred testing library used for Foundry projects. Additionally, the template also comes with an empty starter contract and a simple test, you can easily remove them.

mkdir my-project
cd my-project
forge init 
code my-project

If this is your first time using Foundry, refer to the installation instructions for guidance.

The initialized project has the following structure:

├── lib/
├── script/
├── src/
└── test/

These are the default paths for a Foundry project.

  • lib/ is where to store dependency libraries or external contracts.

  • script/ is where to store scripts for automating tasks, such as contract deployment scripts or contract interaction scripts.

  • src/ is where Solidity source code is placed, usually containing your contract files.

  • test/ is used to store test files.

Clone the sight-oracle-contracts repository.

git clone https://github.com/sight-ai/sight-oracle-contracts

Install OpenZeppelin library.

forge install OpenZeppelin/openzeppelin-contracts --no-commit

Forge can remap dependencies to make them easier to import. Create a remappings.txt file in the root directory, then copy the text below into remappings.txt.

forge-std/=lib/forge-std/src/
@sight-oracle/contracts=lib/sight-oracle-contracts/contracts
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts

Writing Contract

In the src directory, create a new file named Example.sol, and copy the example contract code below into src/Example.sol.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import "@sight-oracle/contracts/Oracle/Types.sol";
import "@sight-oracle/contracts/Oracle/Oracle.sol";
import "@sight-oracle/contracts/Oracle/RequestBuilder.sol";
import "@sight-oracle/contracts/Oracle/ResponseResolver.sol";

contract Example {
    // Use Sight Oracle's RequestBuilder and ResponseResolver to interact with Sight Oracle
    using RequestBuilder for Request;
    using ResponseResolver for CapsulatedValue;

    event OracleCallback(bytes32 indexed reqId);

    bytes32 latestReqId;
    Oracle public oracle;
    CapsulatedValue private _target;

    constructor(address oracle_) payable {
        oracle = Oracle(payable(oracle_));
    }

    function makeRequest() public payable {
        // Initialize new FHE computation request of a single step.
        Request memory r = RequestBuilder.newRequest(
            msg.sender,
            1,
            address(this),
            this.callback.selector, // specify the callback for Oracle
            ""
        );

        // Generate a random encrypted value and store in Sight Network
        r.rand();

        // Send the request via Sight FHE Oracle
        latestReqId = oracle.send(r);
    }

    // only Oracle can call this
    function callback(
        bytes32 reqId,
        CapsulatedValue[] memory values
    ) public onlyOracle {
        // Decode value from Oracle callback
        CapsulatedValue memory result = values[0];

        // Keep this encrypted target value
        _target = result;
        emit OracleCallback(reqId);
    }

    function getLatestReqId() public view returns (bytes32) {
        return latestReqId;
    }

    function getTarget() public view returns (CapsulatedValue memory) {
        return _target;
    }

    modifier onlyOracle() {
        require(msg.sender == address(oracle), "Only Oracle Can Do This");
        _;
    }
}

Explanation

  1. Contract Initialization:

    • The Example contract imports the necessary modules from the Sight Oracle package.

    • The contract uses the RequestBuilder library to construct requests and the ResponseResolver library to interpret the responses.

  2. Constructor:

    • The constructor initializes the contract with the address of the Sight Oracle. This address is used to send requests and handle callbacks.

  3. makeRequest Function:

    • This function initiates a new request to the Sight Oracle.

    • A new request is created using RequestBuilder.newRequest, specifying the sender, the number of steps in the computation, the address to which the callback should be sent, and the callback function.

    • The rand function is called to generate a random encrypted value and store it in the Sight Network.

    • The send function sends the request to the Sight Oracle.

  4. callback Function:

    • This function is called by the Sight Oracle once the computation is complete.

    • It decodes the returned value using the ResponseResolver and stores the encrypted target value in the _target variable.

  5. Modifiers:

    • onlyOracle: Ensures that only the Sight Oracle can call the callback function.

  6. View Function:

    • getLatestReqId: Retrieves the request ID of the most recent request sent to the Oracle.

    • getTarget: Retrieves the target value of the most recent request.

Deploy Contracts

Environment Setup

We will to deploy the Example contract and the Sight Oracle contract to the Sepolia testnet. To do this, we need to configure Foundry by setting up the Sepolia RPC.

NoteīŧšIf you don’t have Sepolia RPC yet, click here to get Sepolia RPC.

PRIVATE_KEY=<YOUR_PRIVATE_KEY>
SEPOLIA_RPC_URL=<SEPOLIA_RPC_URL>
SEPOLIA_ORACLE_CONTRACT_ADDRESS=0xC5ac65f17Ce781E9F325634b6218Dc75a5CF9abF

Add these environment variables to the .env file. SEPOLIA_ORACLE_CONTRACT_ADDRESS is the Oracle address deployed on the Sepolia test network.

Write a deployment script.

Next, create a folder and name it script and create a file in it called DeployExample.s.sol. This is where we will create the deployment script itself.

Copy the following code snippet into the scripts/DeployExample.sol file.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script} from "forge-std/Script.sol";
import {Example} from "../src/Example.sol";

contract DeployExample is Script {
    function run() external {
        address oracleAddress = vm.envAddress(
            "SEPOLIA_ORACLE_CONTRACT_ADDRESS"
        );
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        Example example = new Example(oracleAddress);
        vm.stopBroadcast();
    }
}

Explanation

  • A Script contract in the Foundry framework is a foundational contract that provides various utilities and functionalities required when deploying smart contracts.

  • DeployExample is a deployment script contract, which inherits from the Script contract in the Foundry framework.

  • Use vm.envAddress() and vm.envUint() to read the Oracle contract address on the Sepolia test network and the private key used for deployment from environment variables, respectively.

  • vm.startBroadcast() initiates the recording of all transactions that will be broadcasted to the blockchain, while vm.stopBroadcast() concludes the broadcasting of transactions.

  • new is used to create a new instance of a smart contract. Using new Example(oracleAddress) to deploy a Example contract with the Oracle address argument.

Note: you must be careful when exposing private keys in a .env file and loading them into programs. This is only recommended for use with non-privileged deployers or for local / test setups. For production setups please review the various wallet options that Foundry supports.

Run deployment script

# To load the variables in the .env file
source .env

# To deploy oracle contract and example contract
forge script script/DeployExample.s.sol:DeployExample --rpc-url $SEPOLIA_RPC_URL --broadcast -vvvv  

Explanation

  • script/DeployExample.s.sol:DeployExample is the path to the script you want to run.

  • The --rpc-url option specifies the RPC endpoint, which can be a URL or an existing alias from the [rpc_endpoints] table.

  • The --broadcast parameter will broadcast the transactions to the network.

  • The -vvvv flag will display debug information, including internal calls for each transaction, gas consumption, RPC request details, etc. This is especially useful for debugging and diagnostics.

This should take a little while, since Forge will also wait for the transaction receipts. You should see something like this after a minute or so:

Congratulations! you have successfully deployed the Sight Oracle and Example contracts to the Sepolia testnet.

Send Request

Now, you have successfully deployed the Example contract. Next, you can call the makeRequest function from the Example contract just deployed to send a request. Foundry provides a powerful tool specifically for interacting with on-chain contracts—cast, and we will use this tool to execute the makeRequest call.

Run this code to send a request to the Example contract.

cast send <YOUR_CONTACT_ADDRESS> "makeRequest()" --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY
  • Replace <YOUR_CONTACT_ADDRESS> with the Example contract address you just deployed.

  • The cast send is used to sign and send a transaction; makeRequest() is the function being called. The --private-key option specifies the private key used for signing the transaction.

It may take some time for this transaction to be written to the Sepolia test chain. After about 10 seconds, you should see something like this:

Congratulations, you have successfully sent a Sight Oracle request on the Sepolia test network. Next, you can copy the transactionHash from the log to view the details of the request on Etherscan, as shown in the image below:

Last updated