cartesi / rollups

Cartesi Rollups
30 stars 12 forks source link

Keeping contract ABIs up-to-date #110

Closed guidanoli closed 8 months ago

guidanoli commented 1 year ago

📚 Context

Anyone can interact with Solidity contracts by means of message calls. These message calls have to be formatted in a such a way that the contract can understand and act upon. Solidity specifies the format of these message calls, as well as events, and errors. What usually happens, however, is that information gets lost when these abstractions get converted into low-level EVM instructions. That is why each Solidity contract is often accompanied by a JSON file which explains their interface in high-level Solidity terms. Libraries like ethers-rs use these files, called ABIs, to help developers interact with Solidity contracts, through language-specific bindings.

The problem is that off-chain developers must have the ABIs in order to generate the Rust bindings, and so on-chain developers must keep the ABIs up-to-date with the contracts. Additionally, since these ABIs tend to be quite large, on-chain PRs often get polluted with unnecessarily large diffs. This impacts the productivity of on-chain developers and cleanliness of on-chain PRs.

✔️ Solutions

There are several possible solutions which have been raised. IMO, the third solution seems the most sustainable.

  1. Create a workflow that automatically updates the ABIs when necessary.

    Pros:

    • On-chain developers don't need to update ABIs every time contracts change.

    Cons:

    • We'd have to give write permission to a workflow.
    • Commits would come from a strange user, like a bot, with no signature.
    • The developer would depend on the CI to have the artifacts locally.
  2. Create a workflow that automatically creates/updates a PR that updates the ABIs.

    Pros:

    • Less intrusive than a bot committing directly.
    • On-chain developers don't need to update ABIs every time contracts change.
    • On-chain PRs get much cleaner.

    Cons:

    • Off-chain developers would depend on the PR to be merged to have the latest ABIs.
    • A lot of effort would need to be invested in this solution
  3. Stop committing ABIs. Let off-chain developers generate them locally. [^1]

    Pros:

    • Off-chain developers always have the latest ABIs locally.
    • On-chain developers don't need to update ABIs every time contracts change.
    • On-chain PRs get much cleaner.

    Cons:

    • Off-chain developers might need to have more things installed?

[^1]: This can be made seamless for off-chain developers by altering the offchain/contracts/build.rs file to run yarn and yarn prepack in the onchain/rollups directory every time something in the onchain/rollups/contracts directory changes.

tuler commented 1 year ago

I prefer option 3. offchain uses cargo. onchain uses npm. This requires some kind of cross-framework build tool. Something smart enough so when you do a cargo build in the offchain it knows there is a dependency to the onchain and trigger the onchain build. This is a classic problem of inter-package dependency of monorepos.

There is one framework that I'd like to understand more is https://nx.dev. It supposedly supports cross-framework builds, with the cargo plugin.

guidanoli commented 1 year ago

In addition to option 3, we can separate off-chain and on-chain in different repositories. The on-chain repo would contain:

And generate/export:

All of these artifacts would be published with the same version (since they all describe the same set of contracts). We may also publish alpha versions for other components to run integration tests. The off-chain code would then be able to use the bindings as a Rust crate, without having to worry about generating ABIs etc.

As for contract deployment, this could also live on a repository different from the on-chain repository. This repository would contain:

juztamau5 commented 1 year ago

I'll propose a solution: Generate the ABIs from the bytecode when needed.

We want to preserve the message calls, events, and errors from Solidity. But these can actually be recovered from the bytecode directly using abi-to-sol.

In the current Ultrachess contracts, we depend on Curve, but Curve is compiled from Vyper so all we get is the bytecode. We address that here: https://github.com/Ultrachess/contracts/blob/main/tools/depends/curve/package.sh#L125

guidanoli commented 1 year ago

I'll propose a solution: Generate the ABIs from the bytecode when needed.

Hey, thanks for your contribution! Could you elaborate more on why is generating ABIs from the bytecode favorable to letting solc generate it from the source code directly?

juztamau5 commented 1 year ago

I guess it depends on access to the source. We only use abi-to-sol for Vyper code. If solc can generate interfaces without round-tripping into bytecode then that's probably a better solution.

guidanoli commented 1 year ago

If solc can generate interfaces without round-tripping into bytecode then that's probably a better solution

We use Hardhat and Foundry to build/test our smart contracts, and both frameworks allow us to generate ABIs from Solidity code.