npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

foundry-hardhat-soko-example

v1.2.0

Published

Integration example for Foundry and Soko with Hardhat

Downloads

33

Readme

Foundry and Hardhat Soko integration example

This repository is an example of integration between Forge (Foundry) and the Hardhat Soko plugin.

  • Testing and compilation is handled by Forge,
  • 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 Forge 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 ./out --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 && forge build --skip test --force",
    ...
  }
  ...
}

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(
              "./out",
              { project: "assured-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 ./out --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("assured-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;