hashgraph / hedera-smart-contracts

Contains Hedera Smart Contract Service supporting files
Apache License 2.0
38 stars 53 forks source link

Add support of network forking with Hardhat #718

Open maksimKrukovich opened 5 months ago

maksimKrukovich commented 5 months ago

Problem

It would be really helpful to have the option to fork all Hedera networks with hardhat for testing contracts and scripts. Explanation why forking is the really important thing: https://hardhat.org/hardhat-network/docs/guides/forking-other-networks

Now if you try to fork for example testnet network it's the next error:

ProviderError: Invalid parent hash: 0x5c4eb51347f68986db69a79aea1f64f0a5baa22aae494b64b1204745a7fd3f6f. Expected: 0xb2adf94780dc4a445a2bd41a75e1aff8f91ce016ec283c0012338d75897a1726.

Solution

Add support of network forking with Hardhat

Alternatives

No response

Nana-EC commented 5 months ago

Thanks @maksimKrukovich for the ticket and context. We're in full agreement with you on the value and relevance of forking. It's just a bit tricky.

For context most EVM tools that provide forking support actually run their own EVM client as part of the node, sometimes with custom logic to support that tools features. The challenge here is it's not a clear copy of the network being tested. With networks that don't have any extra EVM logic this is not a problem and is transparent to the users as the client operations are the same.

For Hedera because we have additional functionality such as system contracts that expose native services in smart contracts the tool encounters issues as it doesn't have the logic for these services. It's a challenge for us to easily adopt it as it requires the external tool modifying their logic for our particular chain.

Nonetheless we're actively looking into this and exploring how we can support this in a seamless way for developers like yourself.

That being said if you're not forking contracts with system contract functions you should be fine and forking should work. If you're seeing otherwise please kindly provide some more details to help our investigation.

If your flow relies on system contract functionality then the above issue applies and we're currently blocked in supporting this flow.

We'll continue to explore this challenge though to best enable devs

Nana-EC commented 2 months ago

Revisiting. Forking for contracts using non system contract logic should work fine. if there's an issue there let's explore what hardhat is doing. If it's system contract logic related then we can put this back in the backlog as we need to design that more before being able to address this

georgi-l95 commented 2 months ago

@maksimKrukovich Can you give me a snippet of how you try to fork the network ? Does using npx hardhat node --fork https://testnet.hashio.io/api not work ?

maksimKrukovich commented 1 month ago

@maksimKrukovich Can you give me a snippet of how you try to fork the network ? Does using npx hardhat node --fork https://testnet.hashio.io/api not work ?

Hey @georgi-l95,

Often I do this like that:

  1. Configure the forking in the hardhat.config.ts: forking: { url: process.env.RPC_URL || "", },
  2. yarn hardhat test
  3. And on this step I received the error: ProviderError: Invalid parent hash: 0x41043892a6b4d00731a84a5e825c93774498189dafebe7ee4861205464a2177b. Expected: 0x064cc975bfc2a39bd32ba080fc0c2ef483a1a0313fe03e61648fe13756bb7364.

And it fails on this line: const { ... } = await deployFixture();

I've heard Hedera doesn't support fixtures, so I use deployFixture() instead of loadFixture(deployFixture)

Of course I run a test which involves HTS.

acuarica commented 1 month ago

Hi @maksimKrukovich, thanks for sending this. We are aware of this situation. See https://github.com/hashgraph/hedera-smart-contracts/issues/863#issuecomment-2256014531 for a related issue on forking with HTS.

Would you be able to provide a minimal example on how to reproduce the error you mentioned above? This will help us to provide a more comprehensible solution for forking.

maksimKrukovich commented 1 month ago

Hi @acuarica,

The example contract:

contract Example {
    IERC20 public stakingToken;

    constructor(address _stakingToken) payable {
        stakingToken = IERC20(_stakingToken);

        SafeHTS.safeAssociateToken(address(_stakingToken), address(this));
    }

    function stake(uint256 amount) external {
        SafeHTS.safeTransferToken(address(stakingToken), msg.sender, address(this), int64(uint64(amount)));
    }

The example test:

describe("Example", function () {
    async function deployFixture() {
        const [
            owner,
        ] = await ethers.getSigners();

        let client = Client.forTestnet();

        const operatorPrKey = PrivateKey.fromStringECDSA(process.env.PRIVATE_KEY || '');
        const operatorAccountId = AccountId.fromString(process.env.ACCOUNT_ID || '');

        client.setOperator(
            operatorAccountId,
            operatorPrKey
        );

        const stakingToken = await ethers.getContractAt(
            "ExampleToken",
            stakingTokenAddress
        );

        const Example = await ethers.getContractFactory("Example");
        const example = await Example.deploy(
             stakingTokenAddress,
             { from: owner.address, gasLimit: 3000000, value: ethers.parseUnits("12", 18) }
        );
        await example.waitForDeployment();

        return {
            example,
            stakingToken,
            client,
            owner,
        };
    }

    describe("stake", function () {
        it("Should stake the staking token", async function () {
            const { example, stakingToken } = await deployFixture();
            const amountToStake = ethers.parseUnits("10", 8);

            await stakingToken.approve(example.target, amountToStake);

            const tx = await example.stake(amountToStake);
        });
    });
});