NomicFoundation / hardhat-ignition

Hardhat Ignition is a declarative deployment system that enables you to deploy your smart contracts without navigating the mechanics of the deployment process.
https://hardhat.org
MIT License
97 stars 18 forks source link

Compute stuff using futures #714

Open kevincharm opened 3 months ago

kevincharm commented 3 months ago

Apologies if this is covered somewhere in docs, but I couldn't find it. Is there a way to do more complex stuff with futures? e.g. I need to construct some function call using a future address:

const Rocket = buildModule('Rocket', (m) => {
    const rocketImpl = m.contract('Rocket', [])
    const factoryImpl = m.contract('RocketFactory', [])

    const factoryInitData = RocketFactory__factory.createInterface().encodeFunctionData('init', [
        m.readEventArgument(rocketImpl, 'Deployed', 'deployedAddress'),
    ]) // Obviously this fails

    const factoryProxy = m.contract('ERC1967Proxy', [factoryImpl, factoryInitData])

    return {
        factoryProxy,
    }
})
kanej commented 3 months ago

You can't compute over the value of a future.

We do want to support complex use cases though, to help me understand, the intent here is to allow calldata to be formed from a future (i.e. the deployed address) and then passed into a subsequent call?

So maybe some API enhancements like:

  const deployedAddress = m.readEventArgument(
    rocketImpl,
    "Deployed",
    "deployedAddress"
  );

  const callData = m.encodeCallData([deployedAddress])

  m.call(myContract, "someFunction", [callData]);
kevincharm commented 3 months ago

Yes, almost. I'd like to encode a function call, so it would be convenient to also be able to compute the function selector using the function signature - or something along those lines. I haven't looked into the futures internals properly yet, but is it possible to just expose a lower-level future builder that we could use to build custom stuff? Then common usecases could be added to the module builder api?

kevincharm commented 3 months ago
const initCall = m.encodeFunctionCall('init(address)', deployedAddress)
// or
const initCall = m.encodeFunctionCall('init', ['address'], [deployedAddress])

or if the ABI is available in the module builder at some point, could even infer the function signature types?

alcuadrado commented 3 months ago

Hey, this is really interesting, @kevincharm! Can you tell us a bit more about the need to encode the function call? Are you using some sort of factory that deploys and inits in the same call? Or something like that? We'd like to understand what's the right capability to expose that would enable your usecase.

kevincharm commented 3 months ago

Not exactly - I'm just trying to deploy and initialise a standard ERC1967 proxy (see second argument in constructor here: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Proxy.sol#L26). While the proxy's implementation points to a factory, it's irrelevant in this case.

kiseln commented 2 months ago

Having the same problem. I tried initialization in a separate call like this

    const rocketImpl = m.contract('Rocket', [])
    const factoryImpl = m.contract('RocketFactory', [])

    const factoryInitData = RocketFactory__factory.createInterface().encodeFunctionData('init', [
        m.readEventArgument(rocketImpl, 'Deployed', 'deployedAddress'),
    ]) // Obviously this fails

    const factoryProxy = m.contract('ERC1967Proxy', [factoryImpl])

    m.call(factoryProxy, 'init', [m.readEventArgument(rocketImpl, 'Deployed', 'deployedAddress')]

But this also doesn't work due to validation (method 'init' does not exist on a proxy contract)