hardhat-soko-example

1.0.2 • Public • Published

Foundry and Hardhat Soko integration example

This repository is an example of integration between Hardhat and the Hardhat Soko plugin.

  • Testing and compilation is handled by Hardhat,
  • Compilation artifacts management is handled by Soko,
  • Releases are handled using Changesets,
  • Deployments are handled by Hardhat Deploy and use the managed compilation artifacts.

Development

Development of smart contracts is handled by Hardhat only. There are no particular points related to the integration with Hardhat Soko or else.

Pushing on main

When pushing a commit on main, we would like to update the latest version of our compilation artifacts so that it is accessible to everyone.

This operation is made automatically by the CI, it will

  • perform a fresh compilation of the smart contracts,
  • push it on the storage using the latest tag.

The related code can be found in the main.yml workflow file

---
- name: Create artifacts
  run: npm run compile
- name: Push latest release to storage
  run: npm run hardhat soko push -- --artifact-path ./artifacts --tag latest --force

where compile is a npm script:

{
  ...
  "scripts": {
    "format:contracts": "prettier --write --plugin=prettier-plugin-solidity 'src/**/*.sol'",
    "compile": "npm run format:contracts && hardhat compile --force --no-typechain",
    ...
  }
  ...
}

Creating a pull request

When opening a new pull request or pushing to an existing one, we would like to have an overview of which contracts are added, removed or modified.

In the pr.yml workflow,

  • we perform a fresh compilation of the smart contracts,
  • we pull the latest tag compilation artifact,
  • we diff both artifacts in order to check for changes,
  • if there are changes, we add a comment to the pull request.

The matching code is

      ...
      - name: Create artifacts
        run: npm run compile
      - name: Pull latest release
        run: npm run hardhat soko pull -- --tag latest
      - name: Create diff between artifacts and latest release
        id: artifacts-diff
        uses: actions/github-script@v7
        with:
          script: |
            const { generateDiffWithTargetRelease, LocalStorageProvider } = require("hardhat-soko/scripts");
            const diff = await generateDiffWithTargetRelease(
              "./artifacts",
              { project: "doubtful-counter", tagOrId: "latest" },
               undefined,
               new LocalStorageProvider(".soko")
            );
            return diff;
      - name: Create or update comment on PR
        uses: actions/github-script@v7
        with:
          script: |
          ...

Creating a release

Once we are satisfied with our codebase, we can create a release. By doing so we want to do multiple things

  1. push the compilation artifact using the tag of the release,
  2. release a new version of a NPM package containing the ABIs and the deployed contract addresses.

Management of the releases is made using Changesets, so what follows will be applicable to those using this tool. However, it is easily applicable to any other tools or processes.

The exact step in the main.yml workflow is

---
- name: Create Release Pull Request or Publish to npm
  id: changesets
  uses: changesets/action@v1
  with:
    # Script to run logic logic before actually publishing
    # This is needed as Changesets won't trigger the tags workflow when a new version is published, so we need to do it manually
    # The steps of the script are:
    # 1. Upload the compilation artifact with the new release tag,
    # 2. Download the releases,
    # 3. Build the artifacts for the NPM package,
    # 4. Publish the NPM package
    publish: npm run release
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    AWS_REGION: ${{ secrets.AWS_REGION }}
    AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Where the release is a bash script performing the needed steps:

  1. retrieve the tag from the package.json,
  2. push the compilation artifact with the retrieved tag,
  3. prepare what will be exposed by the NPM package:
    • pull all the compilation artifacts,
    • generate the typings,
    • build the release artifacts for the ABIs,
    • build the release artifacts for the deployments,
  4. publish the NPM package.
#!/bin/bash
set -e

RELEASE_TAG_WITHOUT_PREFIX=$(cat package.json | jq -r '.version')

echo "Publishing release $RELEASE_TAG_WITHOUT_PREFIX"

npm run hardhat soko push -- --artifact-path ./artifacts --tag "v$RELEASE_TAG_WITHOUT_PREFIX" && echo "Successfully pushed release artifact" || echo "Failed to push release, we assume here that this is because the release already exists. Still room for improvement here."

echo "Downloading release artifacts"
npm run hardhat soko pull
npm run hardhat soko typings

echo "Release artifacts downloaded"

echo "Preparing NPM package"
npm run release:build-exposed-abis
npm run release:build-deployments-summary

echo "Publishing NPM package"
npm run changeset publish

Content of the exposed ABIs in NPM package

The ABIs for all contracts organized by tag are exposed through the NPM package.

Content of the exposed deployments in NPM package

Deployment files are named based on the deployed contract and the used tag. It allows us to derive a summary as a JSON file of the various deployments organized by tags and organized by network (chain ID)

{
  // Sepolia chain ID
  "11155111": {
    // Tag used to deploy Counter and IncrementOracle
    "v0.0.1": {
      "Counter": "0x001d35B9e151717C57412262915685e7f6E06ee8",
      "IncrementOracle": "0x2c7a34BA50CD81C54F0cf40b1918E0C865aBE1E4"
    }
  }
}

Deployment of the releases smart contracts

This repository uses Hardhat Deploy in order to deploy the compiled smart contracts.

Deployments are purely based on the compilation artifacts that have been uploaded, hence always working with frozen artifacts.

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { project } from "../.soko-typings";

const TARGET_RELEASE = "v0.0.1";

const deployCounter: DeployFunction = async function (
  hre: HardhatRuntimeEnvironment,
) {
  const { deployer } = await hre.getNamedAccounts();

  const projectUtils = project("doubtful-counter").tag(TARGET_RELEASE);

  const incrementOracleArtifact = await projectUtils.getContractArtifact(
    "src/IncrementOracle.sol:IncrementOracle",
  );

  const incrementOracleDeployment = await hre.deployments.deploy(
    `IncrementOracle@${TARGET_RELEASE}`,
    {
      contract: {
        abi: incrementOracleArtifact.abi,
        bytecode: incrementOracleArtifact.evm.bytecode.object,
        metadata: incrementOracleArtifact.metadata,
      },
      from: deployer,
      log: true,
    },
  );

  const counterArtifact = await projectUtils.getContractArtifact(
    "src/Counter.sol:Counter",
  );
  await hre.deployments.deploy(`Counter@${TARGET_RELEASE}`, {
    contract: {
      abi: counterArtifact.abi,
      bytecode: counterArtifact.evm.bytecode.object,
      metadata: counterArtifact.metadata,
    },
    libraries: {
      "src/IncrementOracle.sol:IncrementOracle":
        incrementOracleDeployment.address,
    },
    from: deployer,
    log: true,
  });
};

export default deployCounter;

Package Sidebar

Install

npm i hardhat-soko-example

Weekly Downloads

3

Version

1.0.2

License

MIT

Unpacked Size

29.2 kB

Total Files

34

Last publish

Collaborators

  • vincentgenod