Skip to content

Non-readable errors for contract-to-contract interactions #1145

@rin-st

Description

@rin-st

Started here

How to reproduce:

ContractA

//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "hardhat/console.sol";
import "./ContractB.sol";

/**
 * Contract A that calls Contract B's getError function
 * @author BuidlGuidl
 */
contract ContractA {
    ContractB public contractB;

    constructor(address _contractBAddress) {
        contractB = ContractB(_contractBAddress);
    }

    /**
     * Function that executes ContractB's getError function
     * This will propagate the B_error() from ContractB
     */
    function getErrorFromB() public {
        console.log("ContractA: About to call ContractB.getError()");
        contractB.getError();
    }
} 

ContractB

//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "hardhat/console.sol";

/**
 * Contract B that contains a function that emits an error
 * @author BuidlGuidl
 */
contract ContractB {
    // Custom error definition
    error B_error();

    /**
     * Function that emits a random error B_error()
     */
    function getError() public pure {
        console.log("ContractB: About to emit B_error()");
        revert B_error();
    }
} 

01_deploy_contracts_ab.ts

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";

/**
 * Deploys ContractB and ContractA
 * ContractB is deployed first, then ContractA is deployed with ContractB's address
 *
 * @param hre HardhatRuntimeEnvironment object.
 */
const deployContractsAB: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
  const { deployer } = await hre.getNamedAccounts();
  const { deploy } = hre.deployments;

  // Deploy ContractB first
  await deploy("ContractB", {
    from: deployer,
    args: [], // ContractB has no constructor arguments
    log: true,
    autoMine: true,
  });

  // Get the deployed ContractB to get its address
  const contractB = await hre.ethers.getContract<Contract>("ContractB", deployer);
  console.log("📦 ContractB deployed at:", await contractB.getAddress());

  // Deploy ContractA with ContractB's address
  await deploy("ContractA", {
    from: deployer,
    args: [await contractB.getAddress()], // Pass ContractB's address to ContractA constructor
    log: true,
    autoMine: true,
  });

  // Get the deployed ContractA
  const contractA = await hre.ethers.getContract<Contract>("ContractA", deployer);
  console.log("📦 ContractA deployed at:", await contractA.getAddress());
  console.log("🔗 ContractA is connected to ContractB at:", await contractA.contractB());
};

export default deployContractsAB;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags ContractsAB
deployContractsAB.tags = ["ContractsAB"];

page.tsx

"use client";

import { useState } from "react";
import Link from "next/link";
import type { NextPage } from "next";
import { useAccount } from "wagmi";
import { BugAntIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";

const Home: NextPage = () => {
  const { address: connectedAddress } = useAccount();
  const [isLoading, setIsLoading] = useState(false);

  const { writeContractAsync: writeContractAAsync } = useScaffoldWriteContract({
    contractName: "ContractA",
  });

  const handleCallContractA = async () => {
    setIsLoading(true);

    try {
      // Call the getErrorFromB function which will throw an error
      await writeContractAAsync({
        functionName: "getErrorFromB",
      });
    } catch (error: any) {
      console.log(error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <>
      <div className="flex items-center flex-col grow pt-10">
        <div className="px-5">
          <h1 className="text-center">
            <span className="block text-2xl mb-2">Welcome to</span>
            <span className="block text-4xl font-bold">Scaffold-ETH 2</span>
          </h1>
          <div className="flex justify-center items-center space-x-2 flex-col">
            <p className="my-2 font-medium">Connected Address:</p>
            <Address address={connectedAddress} />
          </div>

          {/* ContractA Button Section */}
          <div className="flex flex-col items-center mt-8 p-6 bg-base-200 rounded-lg">
            <h2 className="text-xl font-bold mb-4">ContractA Function Call</h2>
            <button
              className={`btn btn-primary ${isLoading ? "loading" : ""}`}
              onClick={handleCallContractA}
              disabled={isLoading}
            >
              {isLoading ? "Calling..." : "Call getErrorFromB()"}
            </button>
          </div>

          <p className="text-center text-lg mt-6">
            Get started by editing{" "}
            <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
              packages/nextjs/app/page.tsx
            </code>
          </p>
          <p className="text-center text-lg">
            Edit your smart contract{" "}
            <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
              YourContract.sol
            </code>{" "}
            in{" "}
            <code className="italic bg-base-300 text-base font-bold max-w-full break-words break-all inline-block">
              packages/hardhat/contracts
            </code>
          </p>
        </div>

        <div className="grow bg-base-300 w-full mt-16 px-8 py-12">
          <div className="flex justify-center items-center gap-12 flex-col md:flex-row">
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <BugAntIcon className="h-8 w-8 fill-secondary" />
              <p>
                Tinker with your smart contract using the{" "}
                <Link href="/debug" passHref className="link">
                  Debug Contracts
                </Link>{" "}
                tab.
              </p>
            </div>
            <div className="flex flex-col bg-base-100 px-10 py-10 text-center items-center max-w-xs rounded-3xl">
              <MagnifyingGlassIcon className="h-8 w-8 fill-secondary" />
              <p>
                Explore your local transactions with the{" "}
                <Link href="/blockexplorer" passHref className="link">
                  Block Explorer
                </Link>{" "}
                tab.
              </p>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Home;

  • yarn chain, yarn deploy, yarn start
  • click getErrorFromB() button from page.tsx. You'll get the error
Image

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions